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