using Ryujinx.Graphics.Device; using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace Ryujinx.Graphics.Gpu.Engine.Threed { /// /// State update callback entry, with the callback function and associated field names. /// readonly struct StateUpdateCallbackEntry { /// /// Callback function, to be called if the register was written as the state needs to be updated. /// public Action Callback { get; } /// /// Name of the state fields (registers) associated with the callback function. /// public string[] FieldNames { get; } /// /// Creates a new state update callback entry. /// /// Callback function, to be called if the register was written as the state needs to be updated /// Name of the state fields (registers) associated with the callback function public StateUpdateCallbackEntry(Action callback, params string[] fieldNames) { Callback = callback; FieldNames = fieldNames; } } /// /// GPU state update tracker. /// /// State type class StateUpdateTracker<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TState> { private const int BlockSize = 0xe00; private const int RegisterSize = sizeof(uint); private readonly byte[] _registerToGroupMapping; private readonly Action[] _callbacks; private ulong _dirtyMask; /// /// Creates a new instance of the state update tracker. /// /// Update tracker callback entries public StateUpdateTracker(StateUpdateCallbackEntry[] entries) { _registerToGroupMapping = new byte[BlockSize]; _callbacks = new Action[entries.Length]; var fieldToDelegate = new Dictionary(); for (int entryIndex = 0; entryIndex < entries.Length; entryIndex++) { var entry = entries[entryIndex]; foreach (var fieldName in entry.FieldNames) { fieldToDelegate.Add(fieldName, entryIndex); } _callbacks[entryIndex] = entry.Callback; } 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); if (fieldToDelegate.TryGetValue(field.Name, out int entryIndex)) { for (int i = 0; i < ((sizeOfField + 3) & ~3); i += 4) { _registerToGroupMapping[(offset + i) / RegisterSize] = (byte)(entryIndex + 1); } } offset += sizeOfField; } Debug.Assert(offset == Unsafe.SizeOf()); } /// /// Sets a register as modified. /// /// Register offset in bytes [MethodImpl(MethodImplOptions.AggressiveInlining)] public void SetDirty(int offset) { uint index = (uint)offset / RegisterSize; if (index < BlockSize) { int groupIndex = Unsafe.Add(ref MemoryMarshal.GetArrayDataReference(_registerToGroupMapping), (IntPtr)index); if (groupIndex != 0) { groupIndex--; _dirtyMask |= 1UL << groupIndex; } } } /// /// Forces a register group as dirty, by index. /// /// Index of the group to be dirtied public void ForceDirty(int groupIndex) { if ((uint)groupIndex >= _callbacks.Length) { throw new ArgumentOutOfRangeException(nameof(groupIndex)); } _dirtyMask |= 1UL << groupIndex; } /// /// Forces all register groups as dirty, triggering a full update on the next call to . /// public void SetAllDirty() { Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8); _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length); } /// /// Check if the given register group is dirty without clearing it. /// /// Index of the group to check /// True if dirty, false otherwise public bool IsDirty(int groupIndex) { return (_dirtyMask & (1UL << groupIndex)) != 0; } /// /// Check all the groups specified by for modification, and update if modified. /// /// Mask, where each bit set corresponds to a group index that should be checked [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Update(ulong checkMask) { ulong mask = _dirtyMask & checkMask; if (mask == 0) { return; } do { int groupIndex = BitOperations.TrailingZeroCount(mask); _callbacks[groupIndex](); mask &= ~(1UL << groupIndex); } while (mask != 0); _dirtyMask &= ~checkMask; } } }