aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdateTracker.cs
blob: 7c730967628b78888ab831a83743c4e44d985a34 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
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
{
    /// <summary>
    /// State update callback entry, with the callback function and associated field names.
    /// </summary>
    readonly struct StateUpdateCallbackEntry
    {
        /// <summary>
        /// Callback function, to be called if the register was written as the state needs to be updated.
        /// </summary>
        public Action Callback { get; }

        /// <summary>
        /// Name of the state fields (registers) associated with the callback function.
        /// </summary>
        public string[] FieldNames { get; }

        /// <summary>
        /// Creates a new state update callback entry.
        /// </summary>
        /// <param name="callback">Callback function, to be called if the register was written as the state needs to be updated</param>
        /// <param name="fieldNames">Name of the state fields (registers) associated with the callback function</param>
        public StateUpdateCallbackEntry(Action callback, params string[] fieldNames)
        {
            Callback = callback;
            FieldNames = fieldNames;
        }
    }

    /// <summary>
    /// GPU state update tracker.
    /// </summary>
    /// <typeparam name="TState">State type</typeparam>
    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;

        /// <summary>
        /// Creates a new instance of the state update tracker.
        /// </summary>
        /// <param name="entries">Update tracker callback entries</param>
        public StateUpdateTracker(StateUpdateCallbackEntry[] entries)
        {
            _registerToGroupMapping = new byte[BlockSize];
            _callbacks = new Action[entries.Length];

            var fieldToDelegate = new Dictionary<string, int>();

            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<TState>());
        }

        /// <summary>
        /// Sets a register as modified.
        /// </summary>
        /// <param name="offset">Register offset in bytes</param>
        [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;
                }
            }
        }

        /// <summary>
        /// Forces a register group as dirty, by index.
        /// </summary>
        /// <param name="groupIndex">Index of the group to be dirtied</param>
        public void ForceDirty(int groupIndex)
        {
            if ((uint)groupIndex >= _callbacks.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(groupIndex));
            }

            _dirtyMask |= 1UL << groupIndex;
        }

        /// <summary>
        /// Forces all register groups as dirty, triggering a full update on the next call to <see cref="Update"/>.
        /// </summary>
        public void SetAllDirty()
        {
            Debug.Assert(_callbacks.Length <= sizeof(ulong) * 8);
            _dirtyMask = ulong.MaxValue >> ((sizeof(ulong) * 8) - _callbacks.Length);
        }

        /// <summary>
        /// Check if the given register group is dirty without clearing it.
        /// </summary>
        /// <param name="groupIndex">Index of the group to check</param>
        /// <returns>True if dirty, false otherwise</returns>
        public bool IsDirty(int groupIndex)
        {
            return (_dirtyMask & (1UL << groupIndex)) != 0;
        }

        /// <summary>
        /// Check all the groups specified by <paramref name="checkMask"/> for modification, and update if modified.
        /// </summary>
        /// <param name="checkMask">Mask, where each bit set corresponds to a group index that should be checked</param>
        [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;
        }
    }
}