aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Memory/CounterCache.cs
blob: 24966df41a2d57b9f38e64dece4ccaef6ffac707 (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
using Ryujinx.Graphics.GAL;
using System.Collections.Generic;

namespace Ryujinx.Graphics.Gpu.Memory
{
    /// <summary>
    /// Represents the GPU counter cache.
    /// </summary>
    class CounterCache
    {
        private readonly struct CounterEntry
        {
            public ulong Address { get; }
            public ICounterEvent Event { get; }

            public CounterEntry(ulong address, ICounterEvent evt)
            {
                Address = address;
                Event = evt;
            }
        }

        private readonly List<CounterEntry> _items;

        /// <summary>
        /// Creates a new instance of the GPU counter cache.
        /// </summary>
        public CounterCache()
        {
            _items = new List<CounterEntry>();
        }

        /// <summary>
        /// Adds a new counter to the counter cache, or updates a existing one.
        /// </summary>
        /// <param name="gpuVa">GPU virtual address where the counter will be written in memory</param>
        /// <param name="evt">The new counter</param>
        public void AddOrUpdate(ulong gpuVa, ICounterEvent evt)
        {
            int index = BinarySearch(gpuVa);

            CounterEntry entry = new(gpuVa, evt);

            if (index < 0)
            {
                _items.Insert(~index, entry);
            }
            else
            {
                _items[index] = entry;
            }
        }

        /// <summary>
        /// Handles removal of counters written to a memory region being unmapped.
        /// </summary>
        /// <param name="sender">Sender object</param>
        /// <param name="e">Event arguments</param>
        public void MemoryUnmappedHandler(object sender, UnmapEventArgs e) => RemoveRange(e.Address, e.Size);

        private void RemoveRange(ulong gpuVa, ulong size)
        {
            int index = BinarySearch(gpuVa + size - 1);

            if (index < 0)
            {
                index = ~index;
            }

            if (index >= _items.Count || !InRange(gpuVa, size, _items[index].Address))
            {
                return;
            }

            int count = 1;

            while (index > 0 && InRange(gpuVa, size, _items[index - 1].Address))
            {
                index--;
                count++;
            }

            // Notify the removed counter events that their result should no longer be written out.
            for (int i = 0; i < count; i++)
            {
                ICounterEvent evt = _items[index + i].Event;
                if (evt != null)
                {
                    evt.Invalid = true;
                }
            }

            _items.RemoveRange(index, count);
        }

        /// <summary>
        /// Checks whenever an address falls inside a given range.
        /// </summary>
        /// <param name="startVa">Range start address</param>
        /// <param name="size">Range size</param>
        /// <param name="gpuVa">Address being checked</param>
        /// <returns>True if the address falls inside the range, false otherwise</returns>
        private static bool InRange(ulong startVa, ulong size, ulong gpuVa)
        {
            return gpuVa >= startVa && gpuVa < startVa + size;
        }

        /// <summary>
        /// Check if any counter value was written to the specified GPU virtual memory address.
        /// </summary>
        /// <param name="gpuVa">GPU virtual address</param>
        /// <returns>True if any counter value was written on the specified address, false otherwise</returns>
        public bool Contains(ulong gpuVa)
        {
            return BinarySearch(gpuVa) >= 0;
        }

        /// <summary>
        /// Flush any counter value written to the specified GPU virtual memory address.
        /// </summary>
        /// <param name="gpuVa">GPU virtual address</param>
        /// <returns>True if any counter value was written on the specified address, false otherwise</returns>
        public bool FindAndFlush(ulong gpuVa)
        {
            int index = BinarySearch(gpuVa);
            if (index > 0)
            {
                _items[index].Event?.Flush();

                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Find any counter event that would write to the specified GPU virtual memory address.
        /// </summary>
        /// <param name="gpuVa">GPU virtual address</param>
        /// <returns>The counter event, or null if not present</returns>
        public ICounterEvent FindEvent(ulong gpuVa)
        {
            int index = BinarySearch(gpuVa);
            if (index > 0)
            {
                return _items[index].Event;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Performs binary search of an address on the list.
        /// </summary>
        /// <param name="address">Address to search</param>
        /// <returns>Index of the item, or complement of the index of the nearest item with lower value</returns>
        private int BinarySearch(ulong address)
        {
            int left = 0;
            int right = _items.Count - 1;

            while (left <= right)
            {
                int range = right - left;

                int middle = left + (range >> 1);

                CounterEntry item = _items[middle];

                if (item.Address == address)
                {
                    return middle;
                }

                if (address < item.Address)
                {
                    right = middle - 1;
                }
                else
                {
                    left = middle + 1;
                }
            }

            return ~left;
        }
    }
}