/ src / Ryujinx.Graphics.Vulkan / PipelineBase.cs
PipelineBase.cs
   1  using Ryujinx.Graphics.GAL;
   2  using Ryujinx.Graphics.Shader;
   3  using Silk.NET.Vulkan;
   4  using System;
   5  using System.Collections.Generic;
   6  using System.Linq;
   7  using System.Numerics;
   8  using System.Runtime.CompilerServices;
   9  using System.Runtime.InteropServices;
  10  using CompareOp = Ryujinx.Graphics.GAL.CompareOp;
  11  using Format = Ryujinx.Graphics.GAL.Format;
  12  using FrontFace = Ryujinx.Graphics.GAL.FrontFace;
  13  using IndexType = Ryujinx.Graphics.GAL.IndexType;
  14  using PolygonMode = Ryujinx.Graphics.GAL.PolygonMode;
  15  using PrimitiveTopology = Ryujinx.Graphics.GAL.PrimitiveTopology;
  16  using Viewport = Ryujinx.Graphics.GAL.Viewport;
  17  
  18  namespace Ryujinx.Graphics.Vulkan
  19  {
  20      class PipelineBase : IDisposable
  21      {
  22          public const int DescriptorSetLayouts = 4;
  23  
  24          public const int UniformSetIndex = 0;
  25          public const int StorageSetIndex = 1;
  26          public const int TextureSetIndex = 2;
  27          public const int ImageSetIndex = 3;
  28  
  29          protected readonly VulkanRenderer Gd;
  30          protected readonly Device Device;
  31          public readonly PipelineCache PipelineCache;
  32  
  33          public readonly AutoFlushCounter AutoFlush;
  34          public readonly Action EndRenderPassDelegate;
  35  
  36          protected PipelineDynamicState DynamicState;
  37          protected bool IsMainPipeline;
  38          private PipelineState _newState;
  39          private bool _graphicsStateDirty;
  40          private bool _computeStateDirty;
  41          private bool _bindingBarriersDirty;
  42          private PrimitiveTopology _topology;
  43  
  44          private ulong _currentPipelineHandle;
  45  
  46          protected Auto<DisposablePipeline> Pipeline;
  47  
  48          protected PipelineBindPoint Pbp;
  49  
  50          protected CommandBufferScoped Cbs;
  51          protected CommandBufferScoped? PreloadCbs;
  52          protected CommandBuffer CommandBuffer;
  53  
  54          public CommandBufferScoped CurrentCommandBuffer => Cbs;
  55  
  56          private ShaderCollection _program;
  57  
  58          protected FramebufferParams FramebufferParams;
  59          private Auto<DisposableFramebuffer> _framebuffer;
  60          private RenderPassHolder _rpHolder;
  61          private Auto<DisposableRenderPass> _renderPass;
  62          private RenderPassHolder _nullRenderPass;
  63          private int _writtenAttachmentCount;
  64  
  65          private bool _framebufferUsingColorWriteMask;
  66  
  67          private ITexture[] _preMaskColors;
  68          private ITexture _preMaskDepthStencil;
  69  
  70          private readonly DescriptorSetUpdater _descriptorSetUpdater;
  71  
  72          private IndexBufferState _indexBuffer;
  73          private IndexBufferPattern _indexBufferPattern;
  74          private readonly BufferState[] _transformFeedbackBuffers;
  75          private readonly VertexBufferState[] _vertexBuffers;
  76          private ulong _vertexBuffersDirty;
  77          protected Rectangle<int> ClearScissor;
  78  
  79          private readonly VertexBufferUpdater _vertexBufferUpdater;
  80  
  81          public IndexBufferPattern QuadsToTrisPattern;
  82          public IndexBufferPattern TriFanToTrisPattern;
  83  
  84          private bool _needsIndexBufferRebind;
  85          private bool _needsTransformFeedbackBuffersRebind;
  86  
  87          private bool _tfEnabled;
  88          private bool _tfActive;
  89  
  90          private FeedbackLoopAspects _feedbackLoop;
  91          private bool _passWritesDepthStencil;
  92  
  93          private readonly PipelineColorBlendAttachmentState[] _storedBlend;
  94          public ulong DrawCount { get; private set; }
  95          public bool RenderPassActive { get; private set; }
  96  
  97          public unsafe PipelineBase(VulkanRenderer gd, Device device)
  98          {
  99              Gd = gd;
 100              Device = device;
 101  
 102              AutoFlush = new AutoFlushCounter(gd);
 103              EndRenderPassDelegate = EndRenderPass;
 104  
 105              var pipelineCacheCreateInfo = new PipelineCacheCreateInfo
 106              {
 107                  SType = StructureType.PipelineCacheCreateInfo,
 108              };
 109  
 110              gd.Api.CreatePipelineCache(device, in pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
 111  
 112              _descriptorSetUpdater = new DescriptorSetUpdater(gd, device);
 113              _vertexBufferUpdater = new VertexBufferUpdater(gd);
 114  
 115              _transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
 116              _vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
 117  
 118              const int EmptyVbSize = 16;
 119  
 120              using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize);
 121              emptyVb.SetData(0, new byte[EmptyVbSize]);
 122              _vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, 0, EmptyVbSize);
 123              _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
 124  
 125              ClearScissor = new Rectangle<int>(0, 0, 0xffff, 0xffff);
 126  
 127              _storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets];
 128  
 129              _newState.Initialize();
 130          }
 131  
 132          public void Initialize()
 133          {
 134              _descriptorSetUpdater.Initialize(IsMainPipeline);
 135  
 136              QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
 137              TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
 138          }
 139  
 140          public unsafe void Barrier()
 141          {
 142              Gd.Barriers.QueueMemoryBarrier();
 143          }
 144  
 145          public void ComputeBarrier()
 146          {
 147              MemoryBarrier memoryBarrier = new()
 148              {
 149                  SType = StructureType.MemoryBarrier,
 150                  SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
 151                  DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
 152              };
 153  
 154              Gd.Api.CmdPipelineBarrier(
 155                  CommandBuffer,
 156                  PipelineStageFlags.ComputeShaderBit,
 157                  PipelineStageFlags.AllCommandsBit,
 158                  0,
 159                  1,
 160                  new ReadOnlySpan<MemoryBarrier>(in memoryBarrier),
 161                  0,
 162                  ReadOnlySpan<BufferMemoryBarrier>.Empty,
 163                  0,
 164                  ReadOnlySpan<ImageMemoryBarrier>.Empty);
 165          }
 166  
 167          public void BeginTransformFeedback(PrimitiveTopology topology)
 168          {
 169              Gd.Barriers.EnableTfbBarriers(true);
 170              _tfEnabled = true;
 171          }
 172  
 173          public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
 174          {
 175              EndRenderPass();
 176  
 177              var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size, true).Value;
 178  
 179              BufferHolder.InsertBufferBarrier(
 180                  Gd,
 181                  Cbs.CommandBuffer,
 182                  dst,
 183                  BufferHolder.DefaultAccessFlags,
 184                  AccessFlags.TransferWriteBit,
 185                  PipelineStageFlags.AllCommandsBit,
 186                  PipelineStageFlags.TransferBit,
 187                  offset,
 188                  size);
 189  
 190              Gd.Api.CmdFillBuffer(CommandBuffer, dst, (ulong)offset, (ulong)size, value);
 191  
 192              BufferHolder.InsertBufferBarrier(
 193                  Gd,
 194                  Cbs.CommandBuffer,
 195                  dst,
 196                  AccessFlags.TransferWriteBit,
 197                  BufferHolder.DefaultAccessFlags,
 198                  PipelineStageFlags.TransferBit,
 199                  PipelineStageFlags.AllCommandsBit,
 200                  offset,
 201                  size);
 202          }
 203  
 204          public unsafe void ClearRenderTargetColor(int index, int layer, int layerCount, ColorF color)
 205          {
 206              if (FramebufferParams == null || !FramebufferParams.IsValidColorAttachment(index))
 207              {
 208                  return;
 209              }
 210  
 211              if (_renderPass == null)
 212              {
 213                  CreateRenderPass();
 214              }
 215  
 216              Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 217  
 218              BeginRenderPass();
 219  
 220              var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
 221              var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
 222              var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
 223  
 224              Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
 225          }
 226  
 227          public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, bool stencilMask)
 228          {
 229              if (FramebufferParams == null || !FramebufferParams.HasDepthStencil)
 230              {
 231                  return;
 232              }
 233  
 234              var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue));
 235              var flags = depthMask ? ImageAspectFlags.DepthBit : 0;
 236  
 237              if (stencilMask)
 238              {
 239                  flags |= ImageAspectFlags.StencilBit;
 240              }
 241  
 242              flags &= FramebufferParams.GetDepthStencilAspectFlags();
 243  
 244              if (flags == ImageAspectFlags.None)
 245              {
 246                  return;
 247              }
 248  
 249              if (_renderPass == null)
 250              {
 251                  CreateRenderPass();
 252              }
 253  
 254              Gd.Barriers.Flush(Cbs, RenderPassActive, _rpHolder, EndRenderPassDelegate);
 255  
 256              BeginRenderPass();
 257  
 258              var attachment = new ClearAttachment(flags, 0, clearValue);
 259              var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
 260  
 261              Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
 262          }
 263  
 264          public unsafe void CommandBufferBarrier()
 265          {
 266              Gd.Barriers.QueueCommandBufferBarrier();
 267          }
 268  
 269          public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
 270          {
 271              EndRenderPass();
 272  
 273              var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, srcOffset, size, false);
 274              var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, dstOffset, size, true);
 275  
 276              BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size);
 277          }
 278  
 279          public void DirtyVertexBuffer(Auto<DisposableBuffer> buffer)
 280          {
 281              for (int i = 0; i < _vertexBuffers.Length; i++)
 282              {
 283                  if (_vertexBuffers[i].BoundEquals(buffer))
 284                  {
 285                      _vertexBuffersDirty |= 1UL << i;
 286                  }
 287              }
 288          }
 289  
 290          public void DirtyIndexBuffer(Auto<DisposableBuffer> buffer)
 291          {
 292              if (_indexBuffer.BoundEquals(buffer))
 293              {
 294                  _needsIndexBufferRebind = true;
 295              }
 296          }
 297  
 298          public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
 299          {
 300              if (!_program.IsLinked)
 301              {
 302                  return;
 303              }
 304  
 305              EndRenderPass();
 306              RecreateComputePipelineIfNeeded();
 307  
 308              Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ);
 309          }
 310  
 311          public void DispatchComputeIndirect(Auto<DisposableBuffer> indirectBuffer, int indirectBufferOffset)
 312          {
 313              if (!_program.IsLinked)
 314              {
 315                  return;
 316              }
 317  
 318              EndRenderPass();
 319              RecreateComputePipelineIfNeeded();
 320  
 321              Gd.Api.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Get(Cbs, indirectBufferOffset, 12).Value, (ulong)indirectBufferOffset);
 322          }
 323  
 324          public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
 325          {
 326              if (vertexCount == 0)
 327              {
 328                  return;
 329              }
 330  
 331              if (!RecreateGraphicsPipelineIfNeeded())
 332              {
 333                  return;
 334              }
 335  
 336              BeginRenderPass();
 337              DrawCount++;
 338  
 339              if (Gd.TopologyUnsupported(_topology))
 340              {
 341                  // Temporarily bind a conversion pattern as an index buffer.
 342                  _needsIndexBufferRebind = true;
 343  
 344                  IndexBufferPattern pattern = _topology switch
 345                  {
 346                      PrimitiveTopology.Quads => QuadsToTrisPattern,
 347                      PrimitiveTopology.TriangleFan or
 348                      PrimitiveTopology.Polygon => TriFanToTrisPattern,
 349                      _ => throw new NotSupportedException($"Unsupported topology: {_topology}"),
 350                  };
 351  
 352                  BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
 353                  var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, handle, false);
 354  
 355                  Gd.Api.CmdBindIndexBuffer(CommandBuffer, buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value, 0, Silk.NET.Vulkan.IndexType.Uint32);
 356  
 357                  BeginRenderPass(); // May have been interrupted to set buffer data.
 358                  ResumeTransformFeedbackInternal();
 359  
 360                  Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
 361              }
 362              else
 363              {
 364                  ResumeTransformFeedbackInternal();
 365  
 366                  Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
 367              }
 368          }
 369  
 370          private void UpdateIndexBufferPattern()
 371          {
 372              IndexBufferPattern pattern = null;
 373  
 374              if (Gd.TopologyUnsupported(_topology))
 375              {
 376                  pattern = _topology switch
 377                  {
 378                      PrimitiveTopology.Quads => QuadsToTrisPattern,
 379                      PrimitiveTopology.TriangleFan or
 380                      PrimitiveTopology.Polygon => TriFanToTrisPattern,
 381                      _ => throw new NotSupportedException($"Unsupported topology: {_topology}"),
 382                  };
 383              }
 384  
 385              if (_indexBufferPattern != pattern)
 386              {
 387                  _indexBufferPattern = pattern;
 388                  _needsIndexBufferRebind = true;
 389              }
 390          }
 391  
 392          public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
 393          {
 394              if (indexCount == 0)
 395              {
 396                  return;
 397              }
 398  
 399              UpdateIndexBufferPattern();
 400  
 401              if (!RecreateGraphicsPipelineIfNeeded())
 402              {
 403                  return;
 404              }
 405  
 406              BeginRenderPass();
 407              DrawCount++;
 408  
 409              if (_indexBufferPattern != null)
 410              {
 411                  // Convert the index buffer into a supported topology.
 412                  IndexBufferPattern pattern = _indexBufferPattern;
 413  
 414                  int convertedCount = pattern.GetConvertedCount(indexCount);
 415  
 416                  if (_needsIndexBufferRebind)
 417                  {
 418                      _indexBuffer.BindConvertedIndexBuffer(Gd, Cbs, firstIndex, indexCount, convertedCount, pattern);
 419  
 420                      _needsIndexBufferRebind = false;
 421                  }
 422  
 423                  BeginRenderPass(); // May have been interrupted to set buffer data.
 424                  ResumeTransformFeedbackInternal();
 425  
 426                  Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)convertedCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
 427              }
 428              else
 429              {
 430                  ResumeTransformFeedbackInternal();
 431  
 432                  Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
 433              }
 434          }
 435  
 436          public void DrawIndexedIndirect(BufferRange indirectBuffer)
 437          {
 438              var buffer = Gd.BufferManager
 439                  .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
 440                  .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
 441  
 442              UpdateIndexBufferPattern();
 443  
 444              if (!RecreateGraphicsPipelineIfNeeded())
 445              {
 446                  return;
 447              }
 448  
 449              BeginRenderPass();
 450              DrawCount++;
 451  
 452              if (_indexBufferPattern != null)
 453              {
 454                  // Convert the index buffer into a supported topology.
 455                  IndexBufferPattern pattern = _indexBufferPattern;
 456  
 457                  Auto<DisposableBuffer> indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect(
 458                      Gd,
 459                      Cbs,
 460                      indirectBuffer,
 461                      BufferRange.Empty,
 462                      pattern,
 463                      false,
 464                      1,
 465                      indirectBuffer.Size);
 466  
 467                  _needsIndexBufferRebind = false;
 468  
 469                  BeginRenderPass(); // May have been interrupted to set buffer data.
 470                  ResumeTransformFeedbackInternal();
 471  
 472                  Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value, 0, 1, (uint)indirectBuffer.Size);
 473              }
 474              else
 475              {
 476                  ResumeTransformFeedbackInternal();
 477  
 478                  Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
 479              }
 480          }
 481  
 482          public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
 483          {
 484              var countBuffer = Gd.BufferManager
 485                  .GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
 486                  .Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
 487  
 488              var buffer = Gd.BufferManager
 489                  .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
 490                  .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
 491  
 492              UpdateIndexBufferPattern();
 493  
 494              if (!RecreateGraphicsPipelineIfNeeded())
 495              {
 496                  return;
 497              }
 498  
 499              BeginRenderPass();
 500              DrawCount++;
 501  
 502              if (_indexBufferPattern != null)
 503              {
 504                  // Convert the index buffer into a supported topology.
 505                  IndexBufferPattern pattern = _indexBufferPattern;
 506  
 507                  Auto<DisposableBuffer> indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect(
 508                      Gd,
 509                      Cbs,
 510                      indirectBuffer,
 511                      parameterBuffer,
 512                      pattern,
 513                      true,
 514                      maxDrawCount,
 515                      stride);
 516  
 517                  _needsIndexBufferRebind = false;
 518  
 519                  BeginRenderPass(); // May have been interrupted to set buffer data.
 520                  ResumeTransformFeedbackInternal();
 521  
 522                  if (Gd.Capabilities.SupportsIndirectParameters)
 523                  {
 524                      Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
 525                          CommandBuffer,
 526                          indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value,
 527                          0,
 528                          countBuffer,
 529                          (ulong)parameterBuffer.Offset,
 530                          (uint)maxDrawCount,
 531                          (uint)stride);
 532                  }
 533                  else
 534                  {
 535                      // This is also fine because the indirect data conversion always zeros
 536                      // the entries that are past the current draw count.
 537  
 538                      Gd.Api.CmdDrawIndexedIndirect(
 539                          CommandBuffer,
 540                          indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value,
 541                          0,
 542                          (uint)maxDrawCount,
 543                          (uint)stride);
 544                  }
 545              }
 546              else
 547              {
 548                  ResumeTransformFeedbackInternal();
 549  
 550                  if (Gd.Capabilities.SupportsIndirectParameters)
 551                  {
 552                      Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
 553                          CommandBuffer,
 554                          buffer,
 555                          (ulong)indirectBuffer.Offset,
 556                          countBuffer,
 557                          (ulong)parameterBuffer.Offset,
 558                          (uint)maxDrawCount,
 559                          (uint)stride);
 560                  }
 561                  else
 562                  {
 563                      // Not fully correct, but we can't do much better if the host does not support indirect count.
 564                      Gd.Api.CmdDrawIndexedIndirect(
 565                          CommandBuffer,
 566                          buffer,
 567                          (ulong)indirectBuffer.Offset,
 568                          (uint)maxDrawCount,
 569                          (uint)stride);
 570                  }
 571              }
 572          }
 573  
 574          public void DrawIndirect(BufferRange indirectBuffer)
 575          {
 576              // TODO: Support quads and other unsupported topologies.
 577  
 578              var buffer = Gd.BufferManager
 579                  .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
 580                  .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
 581  
 582              if (!RecreateGraphicsPipelineIfNeeded())
 583              {
 584                  return;
 585              }
 586  
 587              BeginRenderPass();
 588              ResumeTransformFeedbackInternal();
 589              DrawCount++;
 590  
 591              Gd.Api.CmdDrawIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
 592          }
 593  
 594          public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
 595          {
 596              if (!Gd.Capabilities.SupportsIndirectParameters)
 597              {
 598                  // TODO: Fallback for when this is not supported.
 599                  throw new NotSupportedException();
 600              }
 601  
 602              var buffer = Gd.BufferManager
 603                  .GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
 604                  .Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size, false).Value;
 605  
 606              var countBuffer = Gd.BufferManager
 607                  .GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
 608                  .Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size, false).Value;
 609  
 610              // TODO: Support quads and other unsupported topologies.
 611  
 612              if (!RecreateGraphicsPipelineIfNeeded())
 613              {
 614                  return;
 615              }
 616  
 617              BeginRenderPass();
 618              ResumeTransformFeedbackInternal();
 619              DrawCount++;
 620  
 621              Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
 622                  CommandBuffer,
 623                  buffer,
 624                  (ulong)indirectBuffer.Offset,
 625                  countBuffer,
 626                  (ulong)parameterBuffer.Offset,
 627                  (uint)maxDrawCount,
 628                  (uint)stride);
 629          }
 630  
 631          public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
 632          {
 633              if (texture is TextureView srcTexture)
 634              {
 635                  var oldCullMode = _newState.CullMode;
 636                  var oldStencilTestEnable = _newState.StencilTestEnable;
 637                  var oldDepthTestEnable = _newState.DepthTestEnable;
 638                  var oldDepthWriteEnable = _newState.DepthWriteEnable;
 639                  var oldViewports = DynamicState.Viewports;
 640                  var oldViewportsCount = _newState.ViewportsCount;
 641                  var oldTopology = _topology;
 642  
 643                  _newState.CullMode = CullModeFlags.None;
 644                  _newState.StencilTestEnable = false;
 645                  _newState.DepthTestEnable = false;
 646                  _newState.DepthWriteEnable = false;
 647                  SignalStateChange();
 648  
 649                  Gd.HelperShader.DrawTexture(
 650                      Gd,
 651                      this,
 652                      srcTexture,
 653                      sampler,
 654                      srcRegion,
 655                      dstRegion);
 656  
 657                  _newState.CullMode = oldCullMode;
 658                  _newState.StencilTestEnable = oldStencilTestEnable;
 659                  _newState.DepthTestEnable = oldDepthTestEnable;
 660                  _newState.DepthWriteEnable = oldDepthWriteEnable;
 661                  SetPrimitiveTopology(oldTopology);
 662  
 663                  DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
 664  
 665                  _newState.ViewportsCount = oldViewportsCount;
 666                  SignalStateChange();
 667              }
 668          }
 669  
 670          public void EndTransformFeedback()
 671          {
 672              Gd.Barriers.EnableTfbBarriers(false);
 673              PauseTransformFeedbackInternal();
 674              _tfEnabled = false;
 675          }
 676  
 677          public bool IsCommandBufferActive(CommandBuffer cb)
 678          {
 679              return CommandBuffer.Handle == cb.Handle;
 680          }
 681  
 682          internal void Rebind(Auto<DisposableBuffer> buffer, int offset, int size)
 683          {
 684              _descriptorSetUpdater.Rebind(buffer, offset, size);
 685  
 686              if (_indexBuffer.Overlaps(buffer, offset, size))
 687              {
 688                  _indexBuffer.BindIndexBuffer(Gd, Cbs);
 689              }
 690  
 691              for (int i = 0; i < _vertexBuffers.Length; i++)
 692              {
 693                  if (_vertexBuffers[i].Overlaps(buffer, offset, size))
 694                  {
 695                      _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
 696                  }
 697              }
 698  
 699              _vertexBufferUpdater.Commit(Cbs);
 700          }
 701  
 702          public void SetAlphaTest(bool enable, float reference, CompareOp op)
 703          {
 704              // This is currently handled using shader specialization, as Vulkan does not support alpha test.
 705              // In the future, we may want to use this to write the reference value into the support buffer,
 706              // to avoid creating one version of the shader per reference value used.
 707          }
 708  
 709          public void SetBlendState(AdvancedBlendDescriptor blend)
 710          {
 711              for (int index = 0; index < Constants.MaxRenderTargets; index++)
 712              {
 713                  ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index];
 714  
 715                  if (index == 0)
 716                  {
 717                      var blendOp = blend.Op.Convert();
 718  
 719                      vkBlend = new PipelineColorBlendAttachmentState(
 720                          blendEnable: true,
 721                          colorBlendOp: blendOp,
 722                          alphaBlendOp: blendOp,
 723                          colorWriteMask: vkBlend.ColorWriteMask);
 724  
 725                      if (Gd.Capabilities.SupportsBlendEquationAdvancedNonPreMultipliedSrcColor)
 726                      {
 727                          _newState.AdvancedBlendSrcPreMultiplied = blend.SrcPreMultiplied;
 728                      }
 729  
 730                      if (Gd.Capabilities.SupportsBlendEquationAdvancedCorrelatedOverlap)
 731                      {
 732                          _newState.AdvancedBlendOverlap = blend.Overlap.Convert();
 733                      }
 734                  }
 735                  else
 736                  {
 737                      vkBlend = new PipelineColorBlendAttachmentState(
 738                          colorWriteMask: vkBlend.ColorWriteMask);
 739                  }
 740  
 741                  if (vkBlend.ColorWriteMask == 0)
 742                  {
 743                      _storedBlend[index] = vkBlend;
 744  
 745                      vkBlend = new PipelineColorBlendAttachmentState();
 746                  }
 747              }
 748  
 749              SignalStateChange();
 750          }
 751  
 752          public void SetBlendState(int index, BlendDescriptor blend)
 753          {
 754              ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index];
 755  
 756              if (blend.Enable)
 757              {
 758                  vkBlend.BlendEnable = blend.Enable;
 759                  vkBlend.SrcColorBlendFactor = blend.ColorSrcFactor.Convert();
 760                  vkBlend.DstColorBlendFactor = blend.ColorDstFactor.Convert();
 761                  vkBlend.ColorBlendOp = blend.ColorOp.Convert();
 762                  vkBlend.SrcAlphaBlendFactor = blend.AlphaSrcFactor.Convert();
 763                  vkBlend.DstAlphaBlendFactor = blend.AlphaDstFactor.Convert();
 764                  vkBlend.AlphaBlendOp = blend.AlphaOp.Convert();
 765              }
 766              else
 767              {
 768                  vkBlend = new PipelineColorBlendAttachmentState(
 769                      colorWriteMask: vkBlend.ColorWriteMask);
 770              }
 771  
 772              if (vkBlend.ColorWriteMask == 0)
 773              {
 774                  _storedBlend[index] = vkBlend;
 775  
 776                  vkBlend = new PipelineColorBlendAttachmentState();
 777              }
 778  
 779              DynamicState.SetBlendConstants(
 780                  blend.BlendConstant.Red,
 781                  blend.BlendConstant.Green,
 782                  blend.BlendConstant.Blue,
 783                  blend.BlendConstant.Alpha);
 784  
 785              // Reset advanced blend state back defaults to the cache to help the pipeline cache.
 786              _newState.AdvancedBlendSrcPreMultiplied = true;
 787              _newState.AdvancedBlendDstPreMultiplied = true;
 788              _newState.AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
 789  
 790              SignalStateChange();
 791          }
 792  
 793          public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
 794          {
 795              DynamicState.SetDepthBias(factor, units, clamp);
 796  
 797              _newState.DepthBiasEnable = enables != 0;
 798              SignalStateChange();
 799          }
 800  
 801          public void SetDepthClamp(bool clamp)
 802          {
 803              _newState.DepthClampEnable = clamp;
 804              SignalStateChange();
 805          }
 806  
 807          public void SetDepthMode(DepthMode mode)
 808          {
 809              bool oldMode = _newState.DepthMode;
 810              _newState.DepthMode = mode == DepthMode.MinusOneToOne;
 811              if (_newState.DepthMode != oldMode)
 812              {
 813                  SignalStateChange();
 814              }
 815          }
 816  
 817          public void SetDepthTest(DepthTestDescriptor depthTest)
 818          {
 819              _newState.DepthTestEnable = depthTest.TestEnable;
 820              _newState.DepthWriteEnable = depthTest.WriteEnable;
 821              _newState.DepthCompareOp = depthTest.Func.Convert();
 822  
 823              UpdatePassDepthStencil();
 824              SignalStateChange();
 825          }
 826  
 827          public void SetFaceCulling(bool enable, Face face)
 828          {
 829              _newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
 830              SignalStateChange();
 831          }
 832  
 833          public void SetFrontFace(FrontFace frontFace)
 834          {
 835              _newState.FrontFace = frontFace.Convert();
 836              SignalStateChange();
 837          }
 838  
 839          public void SetImage(ShaderStage stage, int binding, ITexture image)
 840          {
 841              _descriptorSetUpdater.SetImage(Cbs, stage, binding, image);
 842          }
 843  
 844          public void SetImage(int binding, Auto<DisposableImageView> image)
 845          {
 846              _descriptorSetUpdater.SetImage(binding, image);
 847          }
 848  
 849          public void SetImageArray(ShaderStage stage, int binding, IImageArray array)
 850          {
 851              _descriptorSetUpdater.SetImageArray(Cbs, stage, binding, array);
 852          }
 853  
 854          public void SetImageArraySeparate(ShaderStage stage, int setIndex, IImageArray array)
 855          {
 856              _descriptorSetUpdater.SetImageArraySeparate(Cbs, stage, setIndex, array);
 857          }
 858  
 859          public void SetIndexBuffer(BufferRange buffer, IndexType type)
 860          {
 861              if (buffer.Handle != BufferHandle.Null)
 862              {
 863                  _indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert());
 864              }
 865              else
 866              {
 867                  _indexBuffer = IndexBufferState.Null;
 868              }
 869  
 870              _needsIndexBufferRebind = true;
 871          }
 872  
 873          public void SetLineParameters(float width, bool smooth)
 874          {
 875              _newState.LineWidth = width;
 876              SignalStateChange();
 877          }
 878  
 879          public void SetLogicOpState(bool enable, LogicalOp op)
 880          {
 881              _newState.LogicOpEnable = enable;
 882              _newState.LogicOp = op.Convert();
 883              SignalStateChange();
 884          }
 885  
 886          public void SetMultisampleState(MultisampleDescriptor multisample)
 887          {
 888              _newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
 889              _newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
 890              SignalStateChange();
 891          }
 892  
 893          public void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
 894          {
 895              _newState.PatchControlPoints = (uint)vertices;
 896              SignalStateChange();
 897  
 898              // TODO: Default levels (likely needs emulation on shaders?)
 899          }
 900  
 901          public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
 902          {
 903              // TODO.
 904          }
 905  
 906          public void SetPolygonMode(PolygonMode frontMode, PolygonMode backMode)
 907          {
 908              // TODO.
 909          }
 910  
 911          public void SetPrimitiveRestart(bool enable, int index)
 912          {
 913              _newState.PrimitiveRestartEnable = enable;
 914              // TODO: What to do about the index?
 915              SignalStateChange();
 916          }
 917  
 918          public void SetPrimitiveTopology(PrimitiveTopology topology)
 919          {
 920              _topology = topology;
 921  
 922              var vkTopology = Gd.TopologyRemap(topology).Convert();
 923  
 924              _newState.Topology = vkTopology;
 925  
 926              SignalStateChange();
 927          }
 928  
 929          public void SetProgram(IProgram program)
 930          {
 931              var internalProgram = (ShaderCollection)program;
 932              var stages = internalProgram.GetInfos();
 933  
 934              _program = internalProgram;
 935  
 936              _descriptorSetUpdater.SetProgram(Cbs, internalProgram, _currentPipelineHandle != 0);
 937              _bindingBarriersDirty = true;
 938  
 939              _newState.PipelineLayout = internalProgram.PipelineLayout;
 940              _newState.HasTessellationControlShader = internalProgram.HasTessellationControlShader;
 941              _newState.StagesCount = (uint)stages.Length;
 942  
 943              stages.CopyTo(_newState.Stages.AsSpan()[..stages.Length]);
 944  
 945              SignalStateChange();
 946  
 947              if (internalProgram.IsCompute)
 948              {
 949                  EndRenderPass();
 950              }
 951          }
 952  
 953          public void Specialize<T>(in T data) where T : unmanaged
 954          {
 955              var dataSpan = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in data), 1));
 956  
 957              if (!dataSpan.SequenceEqual(_newState.SpecializationData.Span))
 958              {
 959                  _newState.SpecializationData = new SpecData(dataSpan);
 960  
 961                  SignalStateChange();
 962              }
 963          }
 964  
 965          protected virtual void SignalAttachmentChange()
 966          {
 967          }
 968  
 969          public void SetRasterizerDiscard(bool discard)
 970          {
 971              _newState.RasterizerDiscardEnable = discard;
 972              SignalStateChange();
 973  
 974              if (!discard && Gd.IsQualcommProprietary)
 975              {
 976                  // On Adreno, enabling rasterizer discard somehow corrupts the viewport state.
 977                  // Force it to be updated on next use to work around this bug.
 978                  DynamicState.ForceAllDirty();
 979              }
 980          }
 981  
 982          public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
 983          {
 984              int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
 985              int writtenAttachments = 0;
 986  
 987              for (int i = 0; i < count; i++)
 988              {
 989                  ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
 990                  var newMask = (ColorComponentFlags)componentMask[i];
 991  
 992                  // When color write mask is 0, remove all blend state to help the pipeline cache.
 993                  // Restore it when the mask becomes non-zero.
 994                  if (vkBlend.ColorWriteMask != newMask)
 995                  {
 996                      if (newMask == 0)
 997                      {
 998                          _storedBlend[i] = vkBlend;
 999  
1000                          vkBlend = new PipelineColorBlendAttachmentState();
1001                      }
1002                      else if (vkBlend.ColorWriteMask == 0)
1003                      {
1004                          vkBlend = _storedBlend[i];
1005                      }
1006                  }
1007  
1008                  vkBlend.ColorWriteMask = newMask;
1009  
1010                  if (componentMask[i] != 0)
1011                  {
1012                      writtenAttachments++;
1013                  }
1014              }
1015  
1016              if (_framebufferUsingColorWriteMask)
1017              {
1018                  SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
1019              }
1020              else
1021              {
1022                  SignalStateChange();
1023  
1024                  if (writtenAttachments != _writtenAttachmentCount)
1025                  {
1026                      SignalAttachmentChange();
1027                      _writtenAttachmentCount = writtenAttachments;
1028                  }
1029              }
1030          }
1031  
1032          private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
1033          {
1034              CreateFramebuffer(colors, depthStencil, filterWriteMasked);
1035              CreateRenderPass();
1036              SignalStateChange();
1037              SignalAttachmentChange();
1038          }
1039  
1040          public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
1041          {
1042              _framebufferUsingColorWriteMask = false;
1043              SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
1044          }
1045  
1046          public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
1047          {
1048              int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
1049              int count = Math.Min(maxScissors, regions.Length);
1050              if (count > 0)
1051              {
1052                  ClearScissor = regions[0];
1053              }
1054  
1055              for (int i = 0; i < count; i++)
1056              {
1057                  var region = regions[i];
1058                  var offset = new Offset2D(region.X, region.Y);
1059                  var extent = new Extent2D((uint)region.Width, (uint)region.Height);
1060  
1061                  DynamicState.SetScissor(i, new Rect2D(offset, extent));
1062              }
1063  
1064              DynamicState.ScissorsCount = count;
1065  
1066              _newState.ScissorsCount = (uint)count;
1067              SignalStateChange();
1068          }
1069  
1070          public void SetStencilTest(StencilTestDescriptor stencilTest)
1071          {
1072              DynamicState.SetStencilMasks(
1073                  (uint)stencilTest.BackFuncMask,
1074                  (uint)stencilTest.BackMask,
1075                  (uint)stencilTest.BackFuncRef,
1076                  (uint)stencilTest.FrontFuncMask,
1077                  (uint)stencilTest.FrontMask,
1078                  (uint)stencilTest.FrontFuncRef);
1079  
1080              _newState.StencilTestEnable = stencilTest.TestEnable;
1081              _newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
1082              _newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
1083              _newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
1084              _newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
1085              _newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
1086              _newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
1087              _newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
1088              _newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
1089  
1090              UpdatePassDepthStencil();
1091              SignalStateChange();
1092          }
1093  
1094          public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
1095          {
1096              _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
1097          }
1098  
1099          public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
1100          {
1101              _descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers);
1102          }
1103  
1104          public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
1105          {
1106              _descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler);
1107          }
1108  
1109          public void SetTextureAndSamplerIdentitySwizzle(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
1110          {
1111              _descriptorSetUpdater.SetTextureAndSamplerIdentitySwizzle(Cbs, stage, binding, texture, sampler);
1112          }
1113  
1114          public void SetTextureArray(ShaderStage stage, int binding, ITextureArray array)
1115          {
1116              _descriptorSetUpdater.SetTextureArray(Cbs, stage, binding, array);
1117          }
1118  
1119          public void SetTextureArraySeparate(ShaderStage stage, int setIndex, ITextureArray array)
1120          {
1121              _descriptorSetUpdater.SetTextureArraySeparate(Cbs, stage, setIndex, array);
1122          }
1123  
1124          public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
1125          {
1126              PauseTransformFeedbackInternal();
1127  
1128              int count = Math.Min(Constants.MaxTransformFeedbackBuffers, buffers.Length);
1129  
1130              for (int i = 0; i < count; i++)
1131              {
1132                  var range = buffers[i];
1133  
1134                  _transformFeedbackBuffers[i].Dispose();
1135  
1136                  if (range.Handle != BufferHandle.Null)
1137                  {
1138                      _transformFeedbackBuffers[i] =
1139                          new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, range.Offset, range.Size, true), range.Offset, range.Size);
1140                      _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
1141                  }
1142                  else
1143                  {
1144                      _transformFeedbackBuffers[i] = BufferState.Null;
1145                  }
1146              }
1147          }
1148  
1149          public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
1150          {
1151              _descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
1152          }
1153  
1154          public void SetUserClipDistance(int index, bool enableClip)
1155          {
1156              // TODO.
1157          }
1158  
1159          public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
1160          {
1161              var formatCapabilities = Gd.FormatCapabilities;
1162  
1163              Span<int> newVbScalarSizes = stackalloc int[Constants.MaxVertexBuffers];
1164  
1165              int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length);
1166              uint dirtyVbSizes = 0;
1167  
1168              for (int i = 0; i < count; i++)
1169              {
1170                  var attribute = vertexAttribs[i];
1171                  var rawIndex = attribute.BufferIndex;
1172                  var bufferIndex = attribute.IsZero ? 0 : rawIndex + 1;
1173  
1174                  if (!attribute.IsZero)
1175                  {
1176                      newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.GetScalarSize());
1177                      dirtyVbSizes |= 1u << rawIndex;
1178                  }
1179  
1180                  _newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription(
1181                      (uint)i,
1182                      (uint)bufferIndex,
1183                      formatCapabilities.ConvertToVertexVkFormat(attribute.Format),
1184                      (uint)attribute.Offset);
1185              }
1186  
1187              while (dirtyVbSizes != 0)
1188              {
1189                  int dirtyBit = BitOperations.TrailingZeroCount(dirtyVbSizes);
1190  
1191                  ref var buffer = ref _vertexBuffers[dirtyBit + 1];
1192  
1193                  if (buffer.AttributeScalarAlignment != newVbScalarSizes[dirtyBit])
1194                  {
1195                      _vertexBuffersDirty |= 1UL << (dirtyBit + 1);
1196                      buffer.AttributeScalarAlignment = newVbScalarSizes[dirtyBit];
1197                  }
1198  
1199                  dirtyVbSizes &= ~(1u << dirtyBit);
1200              }
1201  
1202              _newState.VertexAttributeDescriptionsCount = (uint)count;
1203              SignalStateChange();
1204          }
1205  
1206          public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
1207          {
1208              int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
1209  
1210              _newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
1211  
1212              int validCount = 1;
1213  
1214              BufferHandle lastHandle = default;
1215              Auto<DisposableBuffer> lastBuffer = default;
1216  
1217              for (int i = 0; i < count; i++)
1218              {
1219                  var vertexBuffer = vertexBuffers[i];
1220  
1221                  // TODO: Support divisor > 1
1222                  var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
1223  
1224                  if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
1225                  {
1226                      Auto<DisposableBuffer> vb = (vertexBuffer.Buffer.Handle == lastHandle) ? lastBuffer :
1227                          Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
1228  
1229                      lastHandle = vertexBuffer.Buffer.Handle;
1230                      lastBuffer = vb;
1231  
1232                      if (vb != null)
1233                      {
1234                          int binding = i + 1;
1235                          int descriptorIndex = validCount++;
1236  
1237                          _newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription(
1238                              (uint)binding,
1239                              (uint)vertexBuffer.Stride,
1240                              inputRate);
1241  
1242                          int vbSize = vertexBuffer.Buffer.Size;
1243  
1244                          if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
1245                          {
1246                              // AMD has a bug where if offset + stride * count is greater than
1247                              // the size, then the last attribute will have the wrong value.
1248                              // As a workaround, simply use the full buffer size.
1249                              int remainder = vbSize % vertexBuffer.Stride;
1250                              if (remainder != 0)
1251                              {
1252                                  vbSize += vertexBuffer.Stride - remainder;
1253                              }
1254                          }
1255  
1256                          ref var buffer = ref _vertexBuffers[binding];
1257                          int oldScalarAlign = buffer.AttributeScalarAlignment;
1258  
1259                          if (Gd.Capabilities.VertexBufferAlignment < 2 &&
1260                              (vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
1261                          {
1262                              if (!buffer.Matches(vb, descriptorIndex, vertexBuffer.Buffer.Offset, vbSize, vertexBuffer.Stride))
1263                              {
1264                                  buffer.Dispose();
1265  
1266                                  buffer = new VertexBufferState(
1267                                      vb,
1268                                      descriptorIndex,
1269                                      vertexBuffer.Buffer.Offset,
1270                                      vbSize,
1271                                      vertexBuffer.Stride);
1272  
1273                                  buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
1274                              }
1275                          }
1276                          else
1277                          {
1278                              // May need to be rewritten. Bind this buffer before draw.
1279  
1280                              buffer.Dispose();
1281  
1282                              buffer = new VertexBufferState(
1283                                  vertexBuffer.Buffer.Handle,
1284                                  descriptorIndex,
1285                                  vertexBuffer.Buffer.Offset,
1286                                  vbSize,
1287                                  vertexBuffer.Stride);
1288  
1289                              _vertexBuffersDirty |= 1UL << binding;
1290                          }
1291  
1292                          buffer.AttributeScalarAlignment = oldScalarAlign;
1293                      }
1294                  }
1295              }
1296  
1297              _vertexBufferUpdater.Commit(Cbs);
1298  
1299              _newState.VertexBindingDescriptionsCount = (uint)validCount;
1300              SignalStateChange();
1301          }
1302  
1303          public void SetViewports(ReadOnlySpan<Viewport> viewports)
1304          {
1305              int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
1306              int count = Math.Min(maxViewports, viewports.Length);
1307  
1308              static float Clamp(float value)
1309              {
1310                  return Math.Clamp(value, 0f, 1f);
1311              }
1312  
1313              DynamicState.ViewportsCount = (uint)count;
1314  
1315              for (int i = 0; i < count; i++)
1316              {
1317                  var viewport = viewports[i];
1318  
1319                  DynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport(
1320                      viewport.Region.X,
1321                      viewport.Region.Y,
1322                      viewport.Region.Width == 0f ? 1f : viewport.Region.Width,
1323                      viewport.Region.Height == 0f ? 1f : viewport.Region.Height,
1324                      Clamp(viewport.DepthNear),
1325                      Clamp(viewport.DepthFar)));
1326              }
1327  
1328              _newState.ViewportsCount = (uint)count;
1329              SignalStateChange();
1330          }
1331  
1332          public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
1333          {
1334              _indexBuffer.Swap(from, to);
1335  
1336              for (int i = 0; i < _vertexBuffers.Length; i++)
1337              {
1338                  _vertexBuffers[i].Swap(from, to);
1339              }
1340  
1341              for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
1342              {
1343                  _transformFeedbackBuffers[i].Swap(from, to);
1344              }
1345  
1346              _descriptorSetUpdater.SwapBuffer(from, to);
1347  
1348              SignalCommandBufferChange();
1349          }
1350  
1351          public void ForceTextureDirty()
1352          {
1353              _descriptorSetUpdater.ForceTextureDirty();
1354          }
1355  
1356          public void ForceImageDirty()
1357          {
1358              _descriptorSetUpdater.ForceImageDirty();
1359          }
1360  
1361          public unsafe void TextureBarrier()
1362          {
1363              Gd.Barriers.QueueTextureBarrier();
1364          }
1365  
1366          public void TextureBarrierTiled()
1367          {
1368              TextureBarrier();
1369          }
1370  
1371          protected void SignalCommandBufferChange()
1372          {
1373              _needsIndexBufferRebind = true;
1374              _needsTransformFeedbackBuffersRebind = true;
1375              _vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
1376  
1377              _descriptorSetUpdater.SignalCommandBufferChange();
1378              DynamicState.ForceAllDirty();
1379              _currentPipelineHandle = 0;
1380          }
1381  
1382          private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
1383          {
1384              if (filterWriteMasked)
1385              {
1386                  // TBDR GPUs don't work properly if the same attachment is bound to multiple targets,
1387                  // due to each attachment being a copy of the real attachment, rather than a direct write.
1388  
1389                  // Just try to remove duplicate attachments.
1390                  // Save a copy of the array to rebind when mask changes.
1391  
1392                  void MaskOut()
1393                  {
1394                      if (!_framebufferUsingColorWriteMask)
1395                      {
1396                          _preMaskColors = colors.ToArray();
1397                          _preMaskDepthStencil = depthStencil;
1398                      }
1399  
1400                      // If true, then the framebuffer must be recreated when the mask changes.
1401                      _framebufferUsingColorWriteMask = true;
1402                  }
1403  
1404                  // Look for textures that are masked out.
1405  
1406                  for (int i = 0; i < colors.Length; i++)
1407                  {
1408                      if (colors[i] == null)
1409                      {
1410                          continue;
1411                      }
1412  
1413                      ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
1414  
1415                      for (int j = 0; j < i; j++)
1416                      {
1417                          // Check each binding for a duplicate binding before it.
1418  
1419                          if (colors[i] == colors[j])
1420                          {
1421                              // Prefer the binding with no write mask.
1422                              ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j];
1423                              if (vkBlend.ColorWriteMask == 0)
1424                              {
1425                                  colors[i] = null;
1426                                  MaskOut();
1427                              }
1428                              else if (vkBlend2.ColorWriteMask == 0)
1429                              {
1430                                  colors[j] = null;
1431                                  MaskOut();
1432                              }
1433                          }
1434                      }
1435                  }
1436              }
1437  
1438              if (IsMainPipeline)
1439              {
1440                  FramebufferParams?.ClearBindings();
1441              }
1442  
1443              FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
1444  
1445              if (IsMainPipeline)
1446              {
1447                  FramebufferParams.AddBindings();
1448  
1449                  _newState.FeedbackLoopAspects = FeedbackLoopAspects.None;
1450                  _bindingBarriersDirty = true;
1451              }
1452  
1453              _passWritesDepthStencil = false;
1454              UpdatePassDepthStencil();
1455              UpdatePipelineAttachmentFormats();
1456          }
1457  
1458          protected void UpdatePipelineAttachmentFormats()
1459          {
1460              var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
1461              FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
1462              _newState.Internal.AttachmentIntegerFormatMask = FramebufferParams.AttachmentIntegerFormatMask;
1463              _newState.Internal.LogicOpsAllowed = FramebufferParams.LogicOpsAllowed;
1464  
1465              for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
1466              {
1467                  dstAttachmentFormats[i] = 0;
1468              }
1469  
1470              _newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1);
1471              _newState.HasDepthStencil = FramebufferParams.HasDepthStencil;
1472              _newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1;
1473          }
1474  
1475          protected unsafe void CreateRenderPass()
1476          {
1477              var hasFramebuffer = FramebufferParams != null;
1478  
1479              EndRenderPass();
1480  
1481              if (!hasFramebuffer || FramebufferParams.AttachmentsCount == 0)
1482              {
1483                  // Use the null framebuffer.
1484                  _nullRenderPass ??= new RenderPassHolder(Gd, Device, new RenderPassCacheKey(), FramebufferParams);
1485  
1486                  _rpHolder = _nullRenderPass;
1487                  _renderPass = _nullRenderPass.GetRenderPass();
1488                  _framebuffer = _nullRenderPass.GetFramebuffer(Gd, Cbs, FramebufferParams);
1489              }
1490              else
1491              {
1492                  (_rpHolder, _framebuffer) = FramebufferParams.GetPassAndFramebuffer(Gd, Device, Cbs);
1493  
1494                  _renderPass = _rpHolder.GetRenderPass();
1495              }
1496          }
1497  
1498          protected void SignalStateChange()
1499          {
1500              _graphicsStateDirty = true;
1501              _computeStateDirty = true;
1502          }
1503  
1504          private void RecreateComputePipelineIfNeeded()
1505          {
1506              if (_computeStateDirty || Pbp != PipelineBindPoint.Compute)
1507              {
1508                  CreatePipeline(PipelineBindPoint.Compute);
1509                  _computeStateDirty = false;
1510                  Pbp = PipelineBindPoint.Compute;
1511  
1512                  if (_bindingBarriersDirty)
1513                  {
1514                      // Stale barriers may have been activated by switching program. Emit any that are relevant.
1515                      _descriptorSetUpdater.InsertBindingBarriers(Cbs);
1516  
1517                      _bindingBarriersDirty = false;
1518                  }
1519              }
1520  
1521              Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
1522  
1523              _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Compute);
1524          }
1525  
1526          private bool ChangeFeedbackLoop(FeedbackLoopAspects aspects)
1527          {
1528              if (_feedbackLoop != aspects)
1529              {
1530                  if (Gd.Capabilities.SupportsDynamicAttachmentFeedbackLoop)
1531                  {
1532                      DynamicState.SetFeedbackLoop(aspects);
1533                  }
1534                  else
1535                  {
1536                      _newState.FeedbackLoopAspects = aspects;
1537                  }
1538  
1539                  _feedbackLoop = aspects;
1540  
1541                  return true;
1542              }
1543  
1544              return false;
1545          }
1546  
1547          [MethodImpl(MethodImplOptions.AggressiveInlining)]
1548          private bool UpdateFeedbackLoop()
1549          {
1550              List<TextureView> hazards = _descriptorSetUpdater.FeedbackLoopHazards;
1551  
1552              if ((hazards?.Count ?? 0) > 0)
1553              {
1554                  FeedbackLoopAspects aspects = 0;
1555  
1556                  foreach (TextureView view in hazards)
1557                  {
1558                      // May need to enforce feedback loop layout here in the future.
1559                      // Though technically, it should always work with the general layout.
1560  
1561                      if (view.Info.Format.IsDepthOrStencil())
1562                      {
1563                          if (_passWritesDepthStencil)
1564                          {
1565                              // If depth/stencil isn't written in the pass, it doesn't count as a feedback loop.
1566  
1567                              aspects |= FeedbackLoopAspects.Depth;
1568                          }
1569                      }
1570                      else
1571                      {
1572                          aspects |= FeedbackLoopAspects.Color;
1573                      }
1574                  }
1575  
1576                  return ChangeFeedbackLoop(aspects);
1577              }
1578              else if (_feedbackLoop != 0)
1579              {
1580                  return ChangeFeedbackLoop(FeedbackLoopAspects.None);
1581              }
1582  
1583              return false;
1584          }
1585  
1586          private void UpdatePassDepthStencil()
1587          {
1588              if (!RenderPassActive)
1589              {
1590                  _passWritesDepthStencil = false;
1591              }
1592  
1593              // Stencil test being enabled doesn't necessarily mean a write, but it's not critical to check.
1594              _passWritesDepthStencil |= (_newState.DepthTestEnable && _newState.DepthWriteEnable) || _newState.StencilTestEnable;
1595          }
1596  
1597          private bool RecreateGraphicsPipelineIfNeeded()
1598          {
1599              if (AutoFlush.ShouldFlushDraw(DrawCount))
1600              {
1601                  Gd.FlushAllCommands();
1602              }
1603  
1604              DynamicState.ReplayIfDirty(Gd, CommandBuffer);
1605  
1606              if (_needsIndexBufferRebind && _indexBufferPattern == null)
1607              {
1608                  _indexBuffer.BindIndexBuffer(Gd, Cbs);
1609                  _needsIndexBufferRebind = false;
1610              }
1611  
1612              if (_needsTransformFeedbackBuffersRebind)
1613              {
1614                  PauseTransformFeedbackInternal();
1615  
1616                  for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++)
1617                  {
1618                      _transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
1619                  }
1620  
1621                  _needsTransformFeedbackBuffersRebind = false;
1622              }
1623  
1624              if (_vertexBuffersDirty != 0)
1625              {
1626                  while (_vertexBuffersDirty != 0)
1627                  {
1628                      int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
1629  
1630                      _vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
1631  
1632                      _vertexBuffersDirty &= ~(1UL << i);
1633                  }
1634  
1635                  _vertexBufferUpdater.Commit(Cbs);
1636              }
1637  
1638              if (_bindingBarriersDirty)
1639              {
1640                  // Stale barriers may have been activated by switching program. Emit any that are relevant.
1641                  _descriptorSetUpdater.InsertBindingBarriers(Cbs);
1642  
1643                  _bindingBarriersDirty = false;
1644              }
1645  
1646              if (UpdateFeedbackLoop() || _graphicsStateDirty || Pbp != PipelineBindPoint.Graphics)
1647              {
1648                  if (!CreatePipeline(PipelineBindPoint.Graphics))
1649                  {
1650                      return false;
1651                  }
1652  
1653                  _graphicsStateDirty = false;
1654                  Pbp = PipelineBindPoint.Graphics;
1655              }
1656  
1657              Gd.Barriers.Flush(Cbs, _program, _feedbackLoop != 0, RenderPassActive, _rpHolder, EndRenderPassDelegate);
1658  
1659              _descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, PipelineBindPoint.Graphics);
1660  
1661              return true;
1662          }
1663  
1664          private bool CreatePipeline(PipelineBindPoint pbp)
1665          {
1666              // We can only create a pipeline if the have the shader stages set.
1667              if (_newState.Stages != null)
1668              {
1669                  if (pbp == PipelineBindPoint.Graphics && _renderPass == null)
1670                  {
1671                      CreateRenderPass();
1672                  }
1673  
1674                  if (!_program.IsLinked)
1675                  {
1676                      // Background compile failed, we likely can't create the pipeline because the shader is broken
1677                      // or the driver failed to compile it.
1678  
1679                      return false;
1680                  }
1681  
1682                  var pipeline = pbp == PipelineBindPoint.Compute
1683                      ? _newState.CreateComputePipeline(Gd, Device, _program, PipelineCache)
1684                      : _newState.CreateGraphicsPipeline(Gd, Device, _program, PipelineCache, _renderPass.Get(Cbs).Value);
1685  
1686                  if (pipeline == null)
1687                  {
1688                      // Host failed to create the pipeline, likely due to driver bugs.
1689  
1690                      return false;
1691                  }
1692  
1693                  ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle;
1694  
1695                  if (_currentPipelineHandle != pipelineHandle)
1696                  {
1697                      _currentPipelineHandle = pipelineHandle;
1698                      Pipeline = pipeline;
1699  
1700                      PauseTransformFeedbackInternal();
1701                      Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value);
1702                  }
1703              }
1704  
1705              return true;
1706          }
1707  
1708          private unsafe void BeginRenderPass()
1709          {
1710              if (!RenderPassActive)
1711              {
1712                  FramebufferParams.InsertLoadOpBarriers(Gd, Cbs);
1713  
1714                  var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
1715                  var clearValue = new ClearValue();
1716  
1717                  var renderPassBeginInfo = new RenderPassBeginInfo
1718                  {
1719                      SType = StructureType.RenderPassBeginInfo,
1720                      RenderPass = _renderPass.Get(Cbs).Value,
1721                      Framebuffer = _framebuffer.Get(Cbs).Value,
1722                      RenderArea = renderArea,
1723                      PClearValues = &clearValue,
1724                      ClearValueCount = 1,
1725                  };
1726  
1727                  Gd.Api.CmdBeginRenderPass(CommandBuffer, in renderPassBeginInfo, SubpassContents.Inline);
1728                  RenderPassActive = true;
1729              }
1730          }
1731  
1732          public void EndRenderPass()
1733          {
1734              if (RenderPassActive)
1735              {
1736                  FramebufferParams.AddStoreOpUsage();
1737  
1738                  PauseTransformFeedbackInternal();
1739                  Gd.Api.CmdEndRenderPass(CommandBuffer);
1740                  SignalRenderPassEnd();
1741                  RenderPassActive = false;
1742              }
1743          }
1744  
1745          protected virtual void SignalRenderPassEnd()
1746          {
1747          }
1748  
1749          private void PauseTransformFeedbackInternal()
1750          {
1751              if (_tfEnabled && _tfActive)
1752              {
1753                  EndTransformFeedbackInternal();
1754                  _tfActive = false;
1755              }
1756          }
1757  
1758          private void ResumeTransformFeedbackInternal()
1759          {
1760              if (_tfEnabled && !_tfActive)
1761              {
1762                  BeginTransformFeedbackInternal();
1763                  _tfActive = true;
1764              }
1765          }
1766  
1767          private unsafe void BeginTransformFeedbackInternal()
1768          {
1769              Gd.TransformFeedbackApi.CmdBeginTransformFeedback(CommandBuffer, 0, 0, null, null);
1770          }
1771  
1772          private unsafe void EndTransformFeedbackInternal()
1773          {
1774              Gd.TransformFeedbackApi.CmdEndTransformFeedback(CommandBuffer, 0, 0, null, null);
1775          }
1776  
1777          protected virtual void Dispose(bool disposing)
1778          {
1779              if (disposing)
1780              {
1781                  _nullRenderPass?.Dispose();
1782                  _newState.Dispose();
1783                  _descriptorSetUpdater.Dispose();
1784                  _vertexBufferUpdater.Dispose();
1785  
1786                  for (int i = 0; i < _vertexBuffers.Length; i++)
1787                  {
1788                      _vertexBuffers[i].Dispose();
1789                  }
1790  
1791                  for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
1792                  {
1793                      _transformFeedbackBuffers[i].Dispose();
1794                  }
1795  
1796                  Pipeline?.Dispose();
1797  
1798                  unsafe
1799                  {
1800                      Gd.Api.DestroyPipelineCache(Device, PipelineCache, null);
1801                  }
1802              }
1803          }
1804  
1805          public void Dispose()
1806          {
1807              Dispose(true);
1808          }
1809      }
1810  }