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 }