/ src / Ryujinx.Graphics.Vulkan / Queries / CounterQueue.cs
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  }