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