PipelineFull.cs
1 using Ryujinx.Graphics.GAL; 2 using Ryujinx.Graphics.Vulkan.Queries; 3 using Silk.NET.Vulkan; 4 using System; 5 using System.Collections.Generic; 6 7 namespace Ryujinx.Graphics.Vulkan 8 { 9 class PipelineFull : PipelineBase, IPipeline 10 { 11 private const ulong MinByteWeightForFlush = 256 * 1024 * 1024; // MiB 12 13 private readonly List<(QueryPool, bool)> _activeQueries; 14 private CounterQueueEvent _activeConditionalRender; 15 16 private readonly List<BufferedQuery> _pendingQueryCopies; 17 private readonly List<BufferHolder> _activeBufferMirrors; 18 19 private ulong _byteWeight; 20 21 private readonly List<BufferHolder> _backingSwaps; 22 23 public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device) 24 { 25 _activeQueries = new List<(QueryPool, bool)>(); 26 _pendingQueryCopies = new(); 27 _backingSwaps = new(); 28 _activeBufferMirrors = new(); 29 30 CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer; 31 32 IsMainPipeline = true; 33 } 34 35 private void CopyPendingQuery() 36 { 37 foreach (var query in _pendingQueryCopies) 38 { 39 query.PoolCopy(Cbs); 40 } 41 42 _pendingQueryCopies.Clear(); 43 } 44 45 public void ClearRenderTargetColor(int index, int layer, int layerCount, uint componentMask, ColorF color) 46 { 47 if (FramebufferParams == null) 48 { 49 return; 50 } 51 52 if (componentMask != 0xf || Gd.IsQualcommProprietary) 53 { 54 // We can't use CmdClearAttachments if not writing all components, 55 // because on Vulkan, the pipeline state does not affect clears. 56 // On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all. 57 var dstTexture = FramebufferParams.GetColorView(index); 58 if (dstTexture == null) 59 { 60 return; 61 } 62 63 Span<float> clearColor = stackalloc float[4]; 64 clearColor[0] = color.Red; 65 clearColor[1] = color.Green; 66 clearColor[2] = color.Blue; 67 clearColor[3] = color.Alpha; 68 69 // TODO: Clear only the specified layer. 70 Gd.HelperShader.Clear( 71 Gd, 72 dstTexture, 73 clearColor, 74 componentMask, 75 (int)FramebufferParams.Width, 76 (int)FramebufferParams.Height, 77 FramebufferParams.GetAttachmentComponentType(index), 78 ClearScissor); 79 } 80 else 81 { 82 ClearRenderTargetColor(index, layer, layerCount, color); 83 } 84 } 85 86 public void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask) 87 { 88 if (FramebufferParams == null) 89 { 90 return; 91 } 92 93 if ((stencilMask != 0 && stencilMask != 0xff) || Gd.IsQualcommProprietary) 94 { 95 // We can't use CmdClearAttachments if not clearing all (mask is all ones, 0xFF) or none (mask is 0) of the stencil bits, 96 // because on Vulkan, the pipeline state does not affect clears. 97 // On proprietary Adreno drivers, CmdClearAttachments appears to execute out of order, so it's better to not use it at all. 98 var dstTexture = FramebufferParams.GetDepthStencilView(); 99 if (dstTexture == null) 100 { 101 return; 102 } 103 104 // TODO: Clear only the specified layer. 105 Gd.HelperShader.Clear( 106 Gd, 107 dstTexture, 108 depthValue, 109 depthMask, 110 stencilValue, 111 stencilMask, 112 (int)FramebufferParams.Width, 113 (int)FramebufferParams.Height, 114 FramebufferParams.AttachmentFormats[FramebufferParams.AttachmentsCount - 1], 115 ClearScissor); 116 } 117 else 118 { 119 ClearRenderTargetDepthStencil(layer, layerCount, depthValue, depthMask, stencilValue, stencilMask != 0); 120 } 121 } 122 123 public void EndHostConditionalRendering() 124 { 125 if (Gd.Capabilities.SupportsConditionalRendering) 126 { 127 // Gd.ConditionalRenderingApi.CmdEndConditionalRendering(CommandBuffer); 128 } 129 else 130 { 131 // throw new NotSupportedException(); 132 } 133 134 _activeConditionalRender?.ReleaseHostAccess(); 135 _activeConditionalRender = null; 136 } 137 138 public bool TryHostConditionalRendering(ICounterEvent value, ulong compare, bool isEqual) 139 { 140 // Compare an event and a constant value. 141 if (value is CounterQueueEvent evt) 142 { 143 // Easy host conditional rendering when the check matches what GL can do: 144 // - Event is of type samples passed. 145 // - Result is not a combination of multiple queries. 146 // - Comparing against 0. 147 // - Event has not already been flushed. 148 149 if (compare == 0 && evt.Type == CounterType.SamplesPassed && evt.ClearCounter) 150 { 151 if (!value.ReserveForHostAccess()) 152 { 153 // If the event has been flushed, then just use the values on the CPU. 154 // The query object may already be repurposed for another draw (eg. begin + end). 155 return false; 156 } 157 158 if (Gd.Capabilities.SupportsConditionalRendering) 159 { 160 // var buffer = evt.GetBuffer().Get(Cbs, 0, sizeof(long)).Value; 161 // var flags = isEqual ? ConditionalRenderingFlagsEXT.InvertedBitExt : 0; 162 163 // var conditionalRenderingBeginInfo = new ConditionalRenderingBeginInfoEXT 164 // { 165 // SType = StructureType.ConditionalRenderingBeginInfoExt, 166 // Buffer = buffer, 167 // Flags = flags, 168 // }; 169 170 // Gd.ConditionalRenderingApi.CmdBeginConditionalRendering(CommandBuffer, conditionalRenderingBeginInfo); 171 } 172 173 _activeConditionalRender = evt; 174 return true; 175 } 176 } 177 178 // The GPU will flush the queries to CPU and evaluate the condition there instead. 179 180 FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now. 181 return false; 182 } 183 184 public bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual) 185 { 186 FlushPendingQuery(); // The thread will be stalled manually flushing the counter, so flush commands now. 187 return false; 188 } 189 190 private void FlushPendingQuery() 191 { 192 if (AutoFlush.ShouldFlushQuery()) 193 { 194 FlushCommandsImpl(); 195 } 196 } 197 198 public CommandBufferScoped GetPreloadCommandBuffer() 199 { 200 PreloadCbs ??= Gd.CommandBufferPool.Rent(); 201 202 return PreloadCbs.Value; 203 } 204 205 public void FlushCommandsIfWeightExceeding(IAuto disposedResource, ulong byteWeight) 206 { 207 bool usedByCurrentCb = disposedResource.HasCommandBufferDependency(Cbs); 208 209 if (PreloadCbs != null && !usedByCurrentCb) 210 { 211 usedByCurrentCb = disposedResource.HasCommandBufferDependency(PreloadCbs.Value); 212 } 213 214 if (usedByCurrentCb) 215 { 216 // Since we can only free memory after the command buffer that uses a given resource was executed, 217 // keeping the command buffer might cause a high amount of memory to be in use. 218 // To prevent that, we force submit command buffers if the memory usage by resources 219 // in use by the current command buffer is above a given limit, and those resources were disposed. 220 _byteWeight += byteWeight; 221 222 if (_byteWeight >= MinByteWeightForFlush) 223 { 224 FlushCommandsImpl(); 225 } 226 } 227 } 228 229 public void Restore() 230 { 231 if (Pipeline != null) 232 { 233 Gd.Api.CmdBindPipeline(CommandBuffer, Pbp, Pipeline.Get(Cbs).Value); 234 } 235 236 SignalCommandBufferChange(); 237 238 if (Pipeline != null && Pbp == PipelineBindPoint.Graphics) 239 { 240 DynamicState.ReplayIfDirty(Gd, CommandBuffer); 241 } 242 } 243 244 public void FlushCommandsImpl() 245 { 246 AutoFlush.RegisterFlush(DrawCount); 247 EndRenderPass(); 248 249 foreach ((var queryPool, _) in _activeQueries) 250 { 251 Gd.Api.CmdEndQuery(CommandBuffer, queryPool, 0); 252 } 253 254 _byteWeight = 0; 255 256 if (PreloadCbs != null) 257 { 258 PreloadCbs.Value.Dispose(); 259 PreloadCbs = null; 260 } 261 262 Gd.Barriers.Flush(Cbs, false, null, null); 263 CommandBuffer = (Cbs = Gd.CommandBufferPool.ReturnAndRent(Cbs)).CommandBuffer; 264 Gd.RegisterFlush(); 265 266 // Restore per-command buffer state. 267 foreach (BufferHolder buffer in _activeBufferMirrors) 268 { 269 buffer.ClearMirrors(); 270 } 271 272 _activeBufferMirrors.Clear(); 273 274 foreach ((var queryPool, var isOcclusion) in _activeQueries) 275 { 276 bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion; 277 278 Gd.Api.CmdResetQueryPool(CommandBuffer, queryPool, 0, 1); 279 Gd.Api.CmdBeginQuery(CommandBuffer, queryPool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0); 280 } 281 282 Gd.ResetCounterPool(); 283 284 Restore(); 285 } 286 287 public void RegisterActiveMirror(BufferHolder buffer) 288 { 289 _activeBufferMirrors.Add(buffer); 290 } 291 292 public void BeginQuery(BufferedQuery query, QueryPool pool, bool needsReset, bool isOcclusion, bool fromSamplePool) 293 { 294 if (needsReset) 295 { 296 EndRenderPass(); 297 298 Gd.Api.CmdResetQueryPool(CommandBuffer, pool, 0, 1); 299 300 if (fromSamplePool) 301 { 302 // Try reset some additional queries in advance. 303 304 Gd.ResetFutureCounters(CommandBuffer, AutoFlush.GetRemainingQueries()); 305 } 306 } 307 308 bool isPrecise = Gd.Capabilities.SupportsPreciseOcclusionQueries && isOcclusion; 309 Gd.Api.CmdBeginQuery(CommandBuffer, pool, 0, isPrecise ? QueryControlFlags.PreciseBit : 0); 310 311 _activeQueries.Add((pool, isOcclusion)); 312 } 313 314 public void EndQuery(QueryPool pool) 315 { 316 Gd.Api.CmdEndQuery(CommandBuffer, pool, 0); 317 318 for (int i = 0; i < _activeQueries.Count; i++) 319 { 320 if (_activeQueries[i].Item1.Handle == pool.Handle) 321 { 322 _activeQueries.RemoveAt(i); 323 break; 324 } 325 } 326 } 327 328 public void CopyQueryResults(BufferedQuery query) 329 { 330 _pendingQueryCopies.Add(query); 331 332 if (AutoFlush.RegisterPendingQuery()) 333 { 334 FlushCommandsImpl(); 335 } 336 } 337 338 protected override void SignalAttachmentChange() 339 { 340 if (AutoFlush.ShouldFlushAttachmentChange(DrawCount)) 341 { 342 FlushCommandsImpl(); 343 } 344 } 345 346 protected override void SignalRenderPassEnd() 347 { 348 CopyPendingQuery(); 349 } 350 } 351 }