/ src / Ryujinx.Graphics.Vulkan / Queries / BufferedQuery.cs
BufferedQuery.cs
  1  using Ryujinx.Common.Logging;
  2  using Ryujinx.Graphics.GAL;
  3  using Silk.NET.Vulkan;
  4  using System;
  5  using System.Runtime.InteropServices;
  6  using System.Threading;
  7  
  8  namespace Ryujinx.Graphics.Vulkan.Queries
  9  {
 10      class BufferedQuery : IDisposable
 11      {
 12          private const int MaxQueryRetries = 5000;
 13          private const long DefaultValue = unchecked((long)0xFFFFFFFEFFFFFFFE);
 14          private const long DefaultValueInt = 0xFFFFFFFE;
 15          private const ulong HighMask = 0xFFFFFFFF00000000;
 16  
 17          private readonly Vk _api;
 18          private readonly Device _device;
 19          private readonly PipelineFull _pipeline;
 20  
 21          private QueryPool _queryPool;
 22  
 23          private readonly BufferHolder _buffer;
 24          private readonly IntPtr _bufferMap;
 25          private readonly CounterType _type;
 26          private readonly bool _result32Bit;
 27          private readonly bool _isSupported;
 28  
 29          private readonly long _defaultValue;
 30          private int? _resetSequence;
 31  
 32          public unsafe BufferedQuery(VulkanRenderer gd, Device device, PipelineFull pipeline, CounterType type, bool result32Bit)
 33          {
 34              _api = gd.Api;
 35              _device = device;
 36              _pipeline = pipeline;
 37              _type = type;
 38              _result32Bit = result32Bit;
 39  
 40              _isSupported = QueryTypeSupported(gd, type);
 41  
 42              if (_isSupported)
 43              {
 44                  QueryPipelineStatisticFlags flags = type == CounterType.PrimitivesGenerated ?
 45                      QueryPipelineStatisticFlags.GeometryShaderPrimitivesBit : 0;
 46  
 47                  var queryPoolCreateInfo = new QueryPoolCreateInfo
 48                  {
 49                      SType = StructureType.QueryPoolCreateInfo,
 50                      QueryCount = 1,
 51                      QueryType = GetQueryType(type),
 52                      PipelineStatistics = flags,
 53                  };
 54  
 55                  gd.Api.CreateQueryPool(device, in queryPoolCreateInfo, null, out _queryPool).ThrowOnError();
 56              }
 57  
 58              var buffer = gd.BufferManager.Create(gd, sizeof(long), forConditionalRendering: true);
 59  
 60              _bufferMap = buffer.Map(0, sizeof(long));
 61              _defaultValue = result32Bit ? DefaultValueInt : DefaultValue;
 62              Marshal.WriteInt64(_bufferMap, _defaultValue);
 63              _buffer = buffer;
 64          }
 65  
 66          private static bool QueryTypeSupported(VulkanRenderer gd, CounterType type)
 67          {
 68              return type switch
 69              {
 70                  CounterType.SamplesPassed => true,
 71                  CounterType.PrimitivesGenerated => gd.Capabilities.SupportsPipelineStatisticsQuery,
 72                  CounterType.TransformFeedbackPrimitivesWritten => gd.Capabilities.SupportsTransformFeedbackQueries,
 73                  _ => false,
 74              };
 75          }
 76  
 77          private static QueryType GetQueryType(CounterType type)
 78          {
 79              return type switch
 80              {
 81                  CounterType.SamplesPassed => QueryType.Occlusion,
 82                  CounterType.PrimitivesGenerated => QueryType.PipelineStatistics,
 83                  CounterType.TransformFeedbackPrimitivesWritten => QueryType.TransformFeedbackStreamExt,
 84                  _ => QueryType.Occlusion,
 85              };
 86          }
 87  
 88          public Auto<DisposableBuffer> GetBuffer()
 89          {
 90              return _buffer.GetBuffer();
 91          }
 92  
 93          public void Reset()
 94          {
 95              End(false);
 96              Begin(null);
 97          }
 98  
 99          public void Begin(int? resetSequence)
100          {
101              if (_isSupported)
102              {
103                  bool needsReset = resetSequence == null || _resetSequence == null || resetSequence.Value != _resetSequence.Value;
104                  bool isOcclusion = _type == CounterType.SamplesPassed;
105                  _pipeline.BeginQuery(this, _queryPool, needsReset, isOcclusion, isOcclusion && resetSequence != null);
106              }
107              _resetSequence = null;
108          }
109  
110          public void End(bool withResult)
111          {
112              if (_isSupported)
113              {
114                  _pipeline.EndQuery(_queryPool);
115              }
116  
117              if (withResult && _isSupported)
118              {
119                  Marshal.WriteInt64(_bufferMap, _defaultValue);
120                  _pipeline.CopyQueryResults(this);
121              }
122              else
123              {
124                  // Dummy result, just return 0.
125                  Marshal.WriteInt64(_bufferMap, 0);
126              }
127          }
128  
129          private bool WaitingForValue(long data)
130          {
131              return data == _defaultValue ||
132                  (!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask));
133          }
134  
135          public bool TryGetResult(out long result)
136          {
137              result = Marshal.ReadInt64(_bufferMap);
138  
139              return result != _defaultValue;
140          }
141  
142          public long AwaitResult(AutoResetEvent wakeSignal = null)
143          {
144              long data = _defaultValue;
145  
146              if (wakeSignal == null)
147              {
148                  while (WaitingForValue(data))
149                  {
150                      data = Marshal.ReadInt64(_bufferMap);
151                  }
152              }
153              else
154              {
155                  int iterations = 0;
156                  while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
157                  {
158                      data = Marshal.ReadInt64(_bufferMap);
159                      if (WaitingForValue(data))
160                      {
161                          wakeSignal.WaitOne(1);
162                      }
163                  }
164  
165                  if (iterations >= MaxQueryRetries)
166                  {
167                      Logger.Error?.Print(LogClass.Gpu, $"Error: Query result {_type} timed out. Took more than {MaxQueryRetries} tries.");
168                  }
169              }
170  
171              return data;
172          }
173  
174          public void PoolReset(CommandBuffer cmd, int resetSequence)
175          {
176              if (_isSupported)
177              {
178                  _api.CmdResetQueryPool(cmd, _queryPool, 0, 1);
179              }
180  
181              _resetSequence = resetSequence;
182          }
183  
184          public void PoolCopy(CommandBufferScoped cbs)
185          {
186              var buffer = _buffer.GetBuffer(cbs.CommandBuffer, true).Get(cbs, 0, sizeof(long), true).Value;
187  
188              QueryResultFlags flags = QueryResultFlags.ResultWaitBit;
189  
190              if (!_result32Bit)
191              {
192                  flags |= QueryResultFlags.Result64Bit;
193              }
194  
195              _api.CmdCopyQueryPoolResults(
196                  cbs.CommandBuffer,
197                  _queryPool,
198                  0,
199                  1,
200                  buffer,
201                  0,
202                  (ulong)(_result32Bit ? sizeof(int) : sizeof(long)),
203                  flags);
204          }
205  
206          public unsafe void Dispose()
207          {
208              _buffer.Dispose();
209              if (_isSupported)
210              {
211                  _api.DestroyQueryPool(_device, _queryPool, null);
212              }
213              _queryPool = default;
214          }
215      }
216  }