CounterQueue.cs
1 using Ryujinx.Graphics.GAL; 2 using Silk.NET.Vulkan; 3 using System; 4 using System.Collections.Generic; 5 using System.Linq; 6 using System.Threading; 7 8 namespace Ryujinx.Graphics.Vulkan.Queries 9 { 10 class CounterQueue : IDisposable 11 { 12 private const int QueryPoolInitialSize = 100; 13 14 private readonly VulkanRenderer _gd; 15 private readonly Device _device; 16 private readonly PipelineFull _pipeline; 17 18 public CounterType Type { get; } 19 public bool Disposed { get; private set; } 20 21 private readonly Queue<CounterQueueEvent> _events = new(); 22 private CounterQueueEvent _current; 23 24 private ulong _accumulatedCounter; 25 private int _waiterCount; 26 27 private readonly object _lock = new(); 28 29 private readonly Queue<BufferedQuery> _queryPool; 30 private readonly AutoResetEvent _queuedEvent = new(false); 31 private readonly AutoResetEvent _wakeSignal = new(false); 32 private readonly AutoResetEvent _eventConsumed = new(false); 33 34 private readonly Thread _consumerThread; 35 36 public int ResetSequence { get; private set; } 37 38 internal CounterQueue(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type) 39 { 40 _gd = gd; 41 _device = device; 42 _pipeline = pipeline; 43 44 Type = type; 45 46 _queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize); 47 for (int i = 0; i < QueryPoolInitialSize; i++) 48 { 49 // AMD Polaris GPUs on Windows seem to have issues reporting 64-bit query results. 50 _queryPool.Enqueue(new BufferedQuery(_gd, _device, _pipeline, type, gd.IsAmdWindows)); 51 } 52 53 _current = new CounterQueueEvent(this, type, 0); 54 55 _consumerThread = new Thread(EventConsumer); 56 _consumerThread.Start(); 57 } 58 59 public void ResetCounterPool() 60 { 61 ResetSequence++; 62 } 63 64 public void ResetFutureCounters(CommandBuffer cmd, int count) 65 { 66 // Pre-emptively reset queries to avoid render pass splitting. 67 lock (_queryPool) 68 { 69 count = Math.Min(count, _queryPool.Count); 70 71 if (count > 0) 72 { 73 foreach (BufferedQuery query in _queryPool) 74 { 75 query.PoolReset(cmd, ResetSequence); 76 77 if (--count == 0) 78 { 79 break; 80 } 81 } 82 } 83 } 84 } 85 86 private void EventConsumer() 87 { 88 while (!Disposed) 89 { 90 CounterQueueEvent evt = null; 91 lock (_lock) 92 { 93 if (_events.Count > 0) 94 { 95 evt = _events.Dequeue(); 96 } 97 } 98 99 if (evt == null) 100 { 101 _queuedEvent.WaitOne(); // No more events to go through, wait for more. 102 } 103 else 104 { 105 // Spin-wait rather than sleeping if there are any waiters, by passing null instead of the wake signal. 106 evt.TryConsume(ref _accumulatedCounter, true, _waiterCount == 0 ? _wakeSignal : null); 107 } 108 109 if (_waiterCount > 0) 110 { 111 _eventConsumed.Set(); 112 } 113 } 114 } 115 116 internal BufferedQuery GetQueryObject() 117 { 118 // Creating/disposing query objects on a context we're sharing with will cause issues. 119 // So instead, make a lot of query objects on the main thread and reuse them. 120 121 lock (_lock) 122 { 123 if (_queryPool.Count > 0) 124 { 125 BufferedQuery result = _queryPool.Dequeue(); 126 return result; 127 } 128 129 return new BufferedQuery(_gd, _device, _pipeline, Type, _gd.IsAmdWindows); 130 } 131 } 132 133 internal void ReturnQueryObject(BufferedQuery query) 134 { 135 lock (_lock) 136 { 137 // The query will be reset when it dequeues. 138 _queryPool.Enqueue(query); 139 } 140 } 141 142 public CounterQueueEvent QueueReport(EventHandler<ulong> resultHandler, float divisor, ulong lastDrawIndex, bool hostReserved) 143 { 144 CounterQueueEvent result; 145 ulong draws = lastDrawIndex - _current.DrawIndex; 146 147 lock (_lock) 148 { 149 // A query's result only matters if more than one draw was performed during it. 150 // Otherwise, dummy it out and return 0 immediately. 151 152 if (hostReserved) 153 { 154 // This counter event is guaranteed to be available for host conditional rendering. 155 _current.ReserveForHostAccess(); 156 } 157 158 _current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten, divisor); 159 _events.Enqueue(_current); 160 161 _current.OnResult += resultHandler; 162 163 result = _current; 164 165 _current = new CounterQueueEvent(this, Type, lastDrawIndex); 166 } 167 168 _queuedEvent.Set(); 169 170 return result; 171 } 172 173 public void QueueReset(ulong lastDrawIndex) 174 { 175 ulong draws = lastDrawIndex - _current.DrawIndex; 176 177 lock (_lock) 178 { 179 _current.Clear(draws != 0); 180 } 181 } 182 183 public void Flush(bool blocking) 184 { 185 if (!blocking) 186 { 187 // Just wake the consumer thread - it will update the queries. 188 _wakeSignal.Set(); 189 return; 190 } 191 192 lock (_lock) 193 { 194 // Tell the queue to process all events. 195 while (_events.Count > 0) 196 { 197 CounterQueueEvent flush = _events.Peek(); 198 if (!flush.TryConsume(ref _accumulatedCounter, true)) 199 { 200 return; // If not blocking, then return when we encounter an event that is not ready yet. 201 } 202 _events.Dequeue(); 203 } 204 } 205 } 206 207 public void FlushTo(CounterQueueEvent evt) 208 { 209 // Flush the counter queue on the main thread. 210 Interlocked.Increment(ref _waiterCount); 211 212 _wakeSignal.Set(); 213 214 while (!evt.Disposed) 215 { 216 _eventConsumed.WaitOne(1); 217 } 218 219 Interlocked.Decrement(ref _waiterCount); 220 } 221 222 public void Dispose() 223 { 224 lock (_lock) 225 { 226 while (_events.Count > 0) 227 { 228 CounterQueueEvent evt = _events.Dequeue(); 229 230 evt.Dispose(); 231 } 232 233 Disposed = true; 234 } 235 236 _queuedEvent.Set(); 237 238 _consumerThread.Join(); 239 240 _current?.Dispose(); 241 242 foreach (BufferedQuery query in _queryPool) 243 { 244 query.Dispose(); 245 } 246 247 _queuedEvent.Dispose(); 248 _wakeSignal.Dispose(); 249 _eventConsumed.Dispose(); 250 } 251 } 252 }