using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Device { public class DeviceState<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> : IDeviceState where TState : unmanaged { private const int RegisterSize = sizeof(int); public TState State; private uint Size => (uint)(Unsafe.SizeOf() + RegisterSize - 1) / RegisterSize; private readonly Func[] _readCallbacks; private readonly Action[] _writeCallbacks; private readonly Dictionary _fieldNamesForDebug; private readonly Action _debugLogCallback; public DeviceState(IReadOnlyDictionary callbacks = null, Action debugLogCallback = null) { _readCallbacks = new Func[Size]; _writeCallbacks = new Action[Size]; if (debugLogCallback != null) { _fieldNamesForDebug = new Dictionary(); _debugLogCallback = debugLogCallback; } var fields = typeof(TState).GetFields(); int offset = 0; for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) { var field = fields[fieldIndex]; int sizeOfField = SizeCalculator.SizeOf(field.FieldType); for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) { int index = (offset + i) / RegisterSize; if (callbacks != null && callbacks.TryGetValue(field.Name, out var cb)) { if (cb.Read != null) { _readCallbacks[index] = cb.Read; } if (cb.Write != null) { _writeCallbacks[index] = cb.Write; } } } if (debugLogCallback != null) { _fieldNamesForDebug.Add((uint)offset, field.Name); } offset += sizeOfField; } Debug.Assert(offset == Unsafe.SizeOf()); } public int Read(int offset) { uint index = (uint)offset / RegisterSize; if (index < Size) { uint alignedOffset = index * RegisterSize; var readCallback = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_readCallbacks), (IntPtr)index); if (readCallback != null) { return readCallback(); } else { return GetRefUnchecked(alignedOffset); } } return 0; } public void Write(int offset, int data) { uint index = (uint)offset / RegisterSize; if (index < Size) { uint alignedOffset = index * RegisterSize; DebugWrite(alignedOffset, data); GetRefIntAlignedUncheck(index) = data; Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); } } public void WriteWithRedundancyCheck(int offset, int data, out bool changed) { uint index = (uint)offset / RegisterSize; if (index < Size) { uint alignedOffset = index * RegisterSize; DebugWrite(alignedOffset, data); ref var storage = ref GetRefIntAlignedUncheck(index); changed = storage != data; storage = data; Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); } else { changed = false; } } [Conditional("DEBUG")] private void DebugWrite(uint alignedOffset, int data) { if (_fieldNamesForDebug != null && _fieldNamesForDebug.TryGetValue(alignedOffset, out string fieldName)) { _debugLogCallback($"{typeof(TState).Name}.{fieldName} = 0x{data:X}"); } } public ref T GetRef(int offset) where T : unmanaged { if ((uint)(offset + Unsafe.SizeOf()) > Unsafe.SizeOf()) { throw new ArgumentOutOfRangeException(nameof(offset)); } return ref GetRefUnchecked((uint)offset); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ref T GetRefUnchecked(uint offset) where T : unmanaged { return ref Unsafe.As(ref Unsafe.AddByteOffset(ref State, (IntPtr)offset)); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private ref int GetRefIntAlignedUncheck(ulong index) { return ref Unsafe.Add(ref Unsafe.As(ref State), (IntPtr)index); } } }