DeviceState.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Diagnostics; 4 using System.Diagnostics.CodeAnalysis; 5 using System.Runtime.CompilerServices; 6 using System.Runtime.InteropServices; 7 8 namespace Ryujinx.Graphics.Device 9 { 10 public class DeviceState<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> : IDeviceState where TState : unmanaged 11 { 12 private const int RegisterSize = sizeof(int); 13 14 public TState State; 15 16 private static uint Size => (uint)(Unsafe.SizeOf<TState>() + RegisterSize - 1) / RegisterSize; 17 18 private readonly Func<int>[] _readCallbacks; 19 private readonly Action<int>[] _writeCallbacks; 20 21 private readonly Dictionary<uint, string> _fieldNamesForDebug; 22 private readonly Action<string> _debugLogCallback; 23 24 public DeviceState(IReadOnlyDictionary<string, RwCallback> callbacks = null, Action<string> debugLogCallback = null) 25 { 26 _readCallbacks = new Func<int>[Size]; 27 _writeCallbacks = new Action<int>[Size]; 28 29 if (debugLogCallback != null) 30 { 31 _fieldNamesForDebug = new Dictionary<uint, string>(); 32 _debugLogCallback = debugLogCallback; 33 } 34 35 var fields = typeof(TState).GetFields(); 36 int offset = 0; 37 38 for (int fieldIndex = 0; fieldIndex < fields.Length; fieldIndex++) 39 { 40 var field = fields[fieldIndex]; 41 42 var currentFieldOffset = (int)Marshal.OffsetOf<TState>(field.Name); 43 var nextFieldOffset = fieldIndex + 1 == fields.Length ? Unsafe.SizeOf<TState>() : (int)Marshal.OffsetOf<TState>(fields[fieldIndex + 1].Name); 44 45 int sizeOfField = nextFieldOffset - currentFieldOffset; 46 47 for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) 48 { 49 int index = (offset + i) / RegisterSize; 50 51 if (callbacks != null && callbacks.TryGetValue(field.Name, out var cb)) 52 { 53 if (cb.Read != null) 54 { 55 _readCallbacks[index] = cb.Read; 56 } 57 58 if (cb.Write != null) 59 { 60 _writeCallbacks[index] = cb.Write; 61 } 62 } 63 } 64 65 if (debugLogCallback != null) 66 { 67 _fieldNamesForDebug.Add((uint)offset, field.Name); 68 } 69 70 offset += sizeOfField; 71 } 72 73 Debug.Assert(offset == Unsafe.SizeOf<TState>()); 74 } 75 76 public int Read(int offset) 77 { 78 uint index = (uint)offset / RegisterSize; 79 80 if (index < Size) 81 { 82 uint alignedOffset = index * RegisterSize; 83 84 var readCallback = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_readCallbacks), (IntPtr)index); 85 if (readCallback != null) 86 { 87 return readCallback(); 88 } 89 else 90 { 91 return GetRefUnchecked<int>(alignedOffset); 92 } 93 } 94 95 return 0; 96 } 97 98 public void Write(int offset, int data) 99 { 100 uint index = (uint)offset / RegisterSize; 101 102 if (index < Size) 103 { 104 uint alignedOffset = index * RegisterSize; 105 DebugWrite(alignedOffset, data); 106 107 GetRefIntAlignedUncheck(index) = data; 108 109 Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); 110 } 111 } 112 113 public void WriteWithRedundancyCheck(int offset, int data, out bool changed) 114 { 115 uint index = (uint)offset / RegisterSize; 116 117 if (index < Size) 118 { 119 uint alignedOffset = index * RegisterSize; 120 DebugWrite(alignedOffset, data); 121 122 ref var storage = ref GetRefIntAlignedUncheck(index); 123 changed = storage != data; 124 storage = data; 125 126 Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_writeCallbacks), (IntPtr)index)?.Invoke(data); 127 } 128 else 129 { 130 changed = false; 131 } 132 } 133 134 [Conditional("DEBUG")] 135 private void DebugWrite(uint alignedOffset, int data) 136 { 137 if (_fieldNamesForDebug != null && _fieldNamesForDebug.TryGetValue(alignedOffset, out string fieldName)) 138 { 139 _debugLogCallback($"{typeof(TState).Name}.{fieldName} = 0x{data:X}"); 140 } 141 } 142 143 public ref T GetRef<T>(int offset) where T : unmanaged 144 { 145 if ((uint)(offset + Unsafe.SizeOf<T>()) > Unsafe.SizeOf<TState>()) 146 { 147 throw new ArgumentOutOfRangeException(nameof(offset)); 148 } 149 150 return ref GetRefUnchecked<T>((uint)offset); 151 } 152 153 [MethodImpl(MethodImplOptions.AggressiveInlining)] 154 private ref T GetRefUnchecked<T>(uint offset) where T : unmanaged 155 { 156 return ref Unsafe.As<TState, T>(ref Unsafe.AddByteOffset(ref State, (IntPtr)offset)); 157 } 158 159 [MethodImpl(MethodImplOptions.AggressiveInlining)] 160 private ref int GetRefIntAlignedUncheck(ulong index) 161 { 162 return ref Unsafe.Add(ref Unsafe.As<TState, int>(ref State), (IntPtr)index); 163 } 164 } 165 }