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 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 ulong SamplingNumber;
public T Object; public T Object;
@ -14,9 +14,9 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common
public void SetObject(ref T obj) 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(); 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 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; private const ulong MaxEntries = 17;

View file

@ -1,15 +1,15 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.DebugPad
{ {
struct DebugPadState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct DebugPadState : ISampledDataStruct
{ {
public ulong SamplingNumber; public ulong SamplingNumber;
public DebugPadAttribute Attributes; public DebugPadAttribute Attributes;
public DebugPadButton Buttons; public DebugPadButton Buttons;
public AnalogStickState AnalogStickR; public AnalogStickState AnalogStickR;
public AnalogStickState AnalogStickL; public AnalogStickState AnalogStickL;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -2,9 +2,8 @@
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
{ {
// TODO: This seems entirely wrong
[Flags] [Flags]
enum KeyboardModifier : uint enum KeyboardModifier : ulong
{ {
None = 0, None = 0,
Control = 1 << 0, Control = 1 << 0,

View file

@ -1,13 +1,13 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Keyboard
{ {
struct KeyboardState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct KeyboardState : ISampledDataStruct
{ {
public ulong SamplingNumber; public ulong SamplingNumber;
public KeyboardModifier Modifiers; public KeyboardModifier Modifiers;
public KeyboardKey Keys; public KeyboardKey Keys;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -1,8 +1,10 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
{ {
struct MouseState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct MouseState : ISampledDataStruct
{ {
public ulong SamplingNumber; public ulong SamplingNumber;
public int X; public int X;
@ -13,7 +15,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Mouse
public int WheelDeltaY; public int WheelDeltaY;
public MouseButton Buttons; public MouseButton Buttons;
public MouseAttribute Attributes; public MouseAttribute Attributes;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -1,8 +1,10 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{ {
struct NpadCommonState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct NpadCommonState : ISampledDataStruct
{ {
public ulong SamplingNumber; public ulong SamplingNumber;
public NpadButton Buttons; public NpadButton Buttons;
@ -10,7 +12,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
public AnalogStickState AnalogStickR; public AnalogStickState AnalogStickR;
public NpadAttribute Attributes; public NpadAttribute Attributes;
private uint _reserved; private uint _reserved;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -1,15 +1,15 @@
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
{ {
struct NpadGcTriggerState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct NpadGcTriggerState : ISampledDataStruct
{ {
#pragma warning disable CS0649 #pragma warning disable CS0649
public ulong SamplingNumber; public ulong SamplingNumber;
public uint TriggerL; public uint TriggerL;
public uint TriggerR; public uint TriggerR;
#pragma warning restore CS0649 #pragma warning restore CS0649
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -1,9 +1,11 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad 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 DeltaTime;
public ulong SamplingNumber; public ulong SamplingNumber;
@ -13,7 +15,5 @@ namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Npad
public Array9<float> Direction; public Array9<float> Direction;
public SixAxisSensorAttribute Attributes; public SixAxisSensorAttribute Attributes;
private uint _reserved; private uint _reserved;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }

View file

@ -1,15 +1,15 @@
using Ryujinx.Common.Memory; using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common; using Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.Common;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen namespace Ryujinx.HLE.HOS.Services.Hid.Types.SharedMemory.TouchScreen
{ {
struct TouchScreenState : ISampledData [StructLayout(LayoutKind.Sequential, Pack = 1)]
struct TouchScreenState : ISampledDataStruct
{ {
public ulong SamplingNumber; public ulong SamplingNumber;
public int TouchesCount; public int TouchesCount;
private int _reserved; private int _reserved;
public Array16<TouchState> Touches; public Array16<TouchState> Touches;
ulong ISampledData.SamplingNumber => SamplingNumber;
} }
} }