mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-30 20:30:14 +00:00
a64fee29dc
* Flush in the middle of long command buffers. * Vulkan: add situational "Fast Flush" mode The AutoFlushCounter class was added to periodically flush Vulkan command buffers throughout a frame, which reduces latency to the GPU as commands are submitted and processed much sooner. This was done by allowing command buffers to flush when framebuffer attachments changed. However, some games have incredibly long render passes with a large number of draws, and really aggressive data access that forces GPU sync. The Vulkan backend could potentially end up building a single command buffer for 4-5ms if a pass has enough draws, such as in BOTW. In the scenario where sync is waited on immediately after submission, this would have to wait for the completion of a much longer command buffer than usual. The solution is to force command buffer submission periodically in a "fast flush" mode. This will end up splitting render passes, but it will only enable if sync is aggressive enough. This should improve performance in GPU limited scenarios, or in games that aggressively wait on synchronization. In some games, it may only kick in when res scaling. It won't trigger in games like SMO where sync is not an issue. Improves performance in Pokemon Scarlet/Violet (res scaled) and BOTW (in general). * Add conversions in milliseconds next to flush timers.
1742 lines
62 KiB
C#
1742 lines
62 KiB
C#
using Ryujinx.Common;
|
|
using Ryujinx.Graphics.GAL;
|
|
using Ryujinx.Graphics.Shader;
|
|
using Silk.NET.Vulkan;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Ryujinx.Graphics.Vulkan
|
|
{
|
|
class PipelineBase : IDisposable
|
|
{
|
|
public const int DescriptorSetLayouts = 4;
|
|
|
|
public const int UniformSetIndex = 0;
|
|
public const int StorageSetIndex = 1;
|
|
public const int TextureSetIndex = 2;
|
|
public const int ImageSetIndex = 3;
|
|
|
|
protected readonly VulkanRenderer Gd;
|
|
protected readonly Device Device;
|
|
public readonly PipelineCache PipelineCache;
|
|
|
|
public readonly AutoFlushCounter AutoFlush;
|
|
|
|
protected PipelineDynamicState DynamicState;
|
|
private PipelineState _newState;
|
|
private bool _stateDirty;
|
|
private GAL.PrimitiveTopology _topology;
|
|
|
|
private ulong _currentPipelineHandle;
|
|
|
|
protected Auto<DisposablePipeline> Pipeline;
|
|
|
|
protected PipelineBindPoint Pbp;
|
|
|
|
protected CommandBufferScoped Cbs;
|
|
protected CommandBufferScoped? PreloadCbs;
|
|
protected CommandBuffer CommandBuffer;
|
|
|
|
public CommandBufferScoped CurrentCommandBuffer => Cbs;
|
|
|
|
private ShaderCollection _program;
|
|
|
|
private Vector4<float>[] _renderScale = new Vector4<float>[73];
|
|
private int _fragmentScaleCount;
|
|
|
|
protected FramebufferParams FramebufferParams;
|
|
private Auto<DisposableFramebuffer> _framebuffer;
|
|
private Auto<DisposableRenderPass> _renderPass;
|
|
private int _writtenAttachmentCount;
|
|
|
|
private bool _framebufferUsingColorWriteMask;
|
|
|
|
private ITexture[] _preMaskColors;
|
|
private ITexture _preMaskDepthStencil;
|
|
|
|
private readonly DescriptorSetUpdater _descriptorSetUpdater;
|
|
|
|
private IndexBufferState _indexBuffer;
|
|
private IndexBufferPattern _indexBufferPattern;
|
|
private readonly BufferState[] _transformFeedbackBuffers;
|
|
private readonly VertexBufferState[] _vertexBuffers;
|
|
private ulong _vertexBuffersDirty;
|
|
protected Rectangle<int> ClearScissor;
|
|
|
|
public SupportBufferUpdater SupportBufferUpdater;
|
|
public IndexBufferPattern QuadsToTrisPattern;
|
|
public IndexBufferPattern TriFanToTrisPattern;
|
|
|
|
private bool _needsIndexBufferRebind;
|
|
private bool _needsTransformFeedbackBuffersRebind;
|
|
|
|
private bool _tfEnabled;
|
|
private bool _tfActive;
|
|
|
|
private PipelineColorBlendAttachmentState[] _storedBlend;
|
|
|
|
public ulong DrawCount { get; private set; }
|
|
public bool RenderPassActive { get; private set; }
|
|
|
|
public unsafe PipelineBase(VulkanRenderer gd, Device device)
|
|
{
|
|
Gd = gd;
|
|
Device = device;
|
|
|
|
AutoFlush = new AutoFlushCounter(gd);
|
|
|
|
var pipelineCacheCreateInfo = new PipelineCacheCreateInfo()
|
|
{
|
|
SType = StructureType.PipelineCacheCreateInfo
|
|
};
|
|
|
|
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
|
|
|
_descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
|
|
|
|
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
|
_vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
|
|
|
|
const int EmptyVbSize = 16;
|
|
|
|
using var emptyVb = gd.BufferManager.Create(gd, EmptyVbSize);
|
|
emptyVb.SetData(0, new byte[EmptyVbSize]);
|
|
_vertexBuffers[0] = new VertexBufferState(emptyVb.GetBuffer(), 0, 0, EmptyVbSize, 0);
|
|
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
|
|
|
ClearScissor = new Rectangle<int>(0, 0, 0xffff, 0xffff);
|
|
|
|
var defaultScale = new Vector4<float> { X = 1f, Y = 0f, Z = 0f, W = 0f };
|
|
new Span<Vector4<float>>(_renderScale).Fill(defaultScale);
|
|
|
|
_storedBlend = new PipelineColorBlendAttachmentState[Constants.MaxRenderTargets];
|
|
|
|
_newState.Initialize();
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
_descriptorSetUpdater.Initialize();
|
|
|
|
SupportBufferUpdater = new SupportBufferUpdater(Gd);
|
|
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, SupportBuffer.RenderScaleMaxCount);
|
|
|
|
QuadsToTrisPattern = new IndexBufferPattern(Gd, 4, 6, 0, new[] { 0, 1, 2, 0, 2, 3 }, 4, false);
|
|
TriFanToTrisPattern = new IndexBufferPattern(Gd, 3, 3, 2, new[] { int.MinValue, -1, 0 }, 1, true);
|
|
}
|
|
|
|
public unsafe void Barrier()
|
|
{
|
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
|
{
|
|
SType = StructureType.MemoryBarrier,
|
|
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
|
|
};
|
|
|
|
Gd.Api.CmdPipelineBarrier(
|
|
CommandBuffer,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
0,
|
|
1,
|
|
memoryBarrier,
|
|
0,
|
|
null,
|
|
0,
|
|
null);
|
|
}
|
|
|
|
public void ComputeBarrier()
|
|
{
|
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
|
{
|
|
SType = StructureType.MemoryBarrier,
|
|
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
|
|
};
|
|
|
|
Gd.Api.CmdPipelineBarrier(
|
|
CommandBuffer,
|
|
PipelineStageFlags.ComputeShaderBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
0,
|
|
1,
|
|
new ReadOnlySpan<MemoryBarrier>(memoryBarrier),
|
|
0,
|
|
ReadOnlySpan<BufferMemoryBarrier>.Empty,
|
|
0,
|
|
ReadOnlySpan<ImageMemoryBarrier>.Empty);
|
|
}
|
|
|
|
public void BeginTransformFeedback(GAL.PrimitiveTopology topology)
|
|
{
|
|
_tfEnabled = true;
|
|
}
|
|
|
|
public void ClearBuffer(BufferHandle destination, int offset, int size, uint value)
|
|
{
|
|
EndRenderPass();
|
|
|
|
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, offset, size, true).Get(Cbs, offset, size).Value;
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
Gd,
|
|
Cbs.CommandBuffer,
|
|
dst,
|
|
BufferHolder.DefaultAccessFlags,
|
|
AccessFlags.TransferWriteBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.TransferBit,
|
|
offset,
|
|
size);
|
|
|
|
Gd.Api.CmdFillBuffer(CommandBuffer, dst, (ulong)offset, (ulong)size, value);
|
|
|
|
BufferHolder.InsertBufferBarrier(
|
|
Gd,
|
|
Cbs.CommandBuffer,
|
|
dst,
|
|
AccessFlags.TransferWriteBit,
|
|
BufferHolder.DefaultAccessFlags,
|
|
PipelineStageFlags.TransferBit,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
offset,
|
|
size);
|
|
}
|
|
|
|
public unsafe void ClearRenderTargetColor(int index, int layer, int layerCount, ColorF color)
|
|
{
|
|
if (FramebufferParams == null || !FramebufferParams.IsValidColorAttachment(index))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_renderPass == null)
|
|
{
|
|
CreateRenderPass();
|
|
}
|
|
|
|
BeginRenderPass();
|
|
|
|
var clearValue = new ClearValue(new ClearColorValue(color.Red, color.Green, color.Blue, color.Alpha));
|
|
var attachment = new ClearAttachment(ImageAspectFlags.ColorBit, (uint)index, clearValue);
|
|
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
|
|
|
FramebufferParams.InsertClearBarrier(Cbs, index);
|
|
|
|
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
|
}
|
|
|
|
public unsafe void ClearRenderTargetDepthStencil(int layer, int layerCount, float depthValue, bool depthMask, int stencilValue, int stencilMask)
|
|
{
|
|
// TODO: Use stencilMask (fully)
|
|
|
|
if (FramebufferParams == null || !FramebufferParams.HasDepthStencil)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (_renderPass == null)
|
|
{
|
|
CreateRenderPass();
|
|
}
|
|
|
|
BeginRenderPass();
|
|
|
|
var clearValue = new ClearValue(null, new ClearDepthStencilValue(depthValue, (uint)stencilValue));
|
|
var flags = depthMask ? ImageAspectFlags.DepthBit : 0;
|
|
|
|
if (stencilMask != 0)
|
|
{
|
|
flags |= ImageAspectFlags.StencilBit;
|
|
}
|
|
|
|
var attachment = new ClearAttachment(flags, 0, clearValue);
|
|
var clearRect = FramebufferParams.GetClearRect(ClearScissor, layer, layerCount);
|
|
|
|
FramebufferParams.InsertClearBarrierDS(Cbs);
|
|
|
|
Gd.Api.CmdClearAttachments(CommandBuffer, 1, &attachment, 1, &clearRect);
|
|
}
|
|
|
|
public unsafe void CommandBufferBarrier()
|
|
{
|
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
|
{
|
|
SType = StructureType.MemoryBarrier,
|
|
SrcAccessMask = BufferHolder.DefaultAccessFlags,
|
|
DstAccessMask = AccessFlags.IndirectCommandReadBit
|
|
};
|
|
|
|
Gd.Api.CmdPipelineBarrier(
|
|
CommandBuffer,
|
|
PipelineStageFlags.AllCommandsBit,
|
|
PipelineStageFlags.DrawIndirectBit,
|
|
0,
|
|
1,
|
|
memoryBarrier,
|
|
0,
|
|
null,
|
|
0,
|
|
null);
|
|
}
|
|
|
|
public void CopyBuffer(BufferHandle source, BufferHandle destination, int srcOffset, int dstOffset, int size)
|
|
{
|
|
EndRenderPass();
|
|
|
|
var src = Gd.BufferManager.GetBuffer(CommandBuffer, source, srcOffset, size, false);
|
|
var dst = Gd.BufferManager.GetBuffer(CommandBuffer, destination, dstOffset, size, true);
|
|
|
|
BufferHolder.Copy(Gd, Cbs, src, dst, srcOffset, dstOffset, size);
|
|
}
|
|
|
|
public void DirtyVertexBuffer(Auto<DisposableBuffer> buffer)
|
|
{
|
|
for (int i = 0; i < _vertexBuffers.Length; i++)
|
|
{
|
|
if (_vertexBuffers[i].BoundEquals(buffer))
|
|
{
|
|
_vertexBuffersDirty |= 1UL << i;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DirtyIndexBuffer(Auto<DisposableBuffer> buffer)
|
|
{
|
|
if (_indexBuffer.BoundEquals(buffer))
|
|
{
|
|
_needsIndexBufferRebind = true;
|
|
}
|
|
}
|
|
|
|
public void DispatchCompute(int groupsX, int groupsY, int groupsZ)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EndRenderPass();
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Compute);
|
|
|
|
Gd.Api.CmdDispatch(CommandBuffer, (uint)groupsX, (uint)groupsY, (uint)groupsZ);
|
|
}
|
|
|
|
public void DispatchComputeIndirect(Auto<DisposableBuffer> indirectBuffer, int indirectBufferOffset)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EndRenderPass();
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Compute);
|
|
|
|
Gd.Api.CmdDispatchIndirect(CommandBuffer, indirectBuffer.Get(Cbs, indirectBufferOffset, 12).Value, (ulong)indirectBufferOffset);
|
|
}
|
|
|
|
public void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
DrawCount++;
|
|
|
|
if (Gd.TopologyUnsupported(_topology))
|
|
{
|
|
// Temporarily bind a conversion pattern as an index buffer.
|
|
_needsIndexBufferRebind = true;
|
|
|
|
IndexBufferPattern pattern = _topology switch
|
|
{
|
|
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
|
|
GAL.PrimitiveTopology.TriangleFan or
|
|
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
|
|
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
|
|
};
|
|
|
|
BufferHandle handle = pattern.GetRepeatingBuffer(vertexCount, out int indexCount);
|
|
var buffer = Gd.BufferManager.GetBuffer(CommandBuffer, handle, false);
|
|
|
|
Gd.Api.CmdBindIndexBuffer(CommandBuffer, buffer.Get(Cbs, 0, indexCount * sizeof(int)).Value, 0, Silk.NET.Vulkan.IndexType.Uint32);
|
|
|
|
BeginRenderPass(); // May have been interrupted to set buffer data.
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
|
|
}
|
|
else
|
|
{
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDraw(CommandBuffer, (uint)vertexCount, (uint)instanceCount, (uint)firstVertex, (uint)firstInstance);
|
|
}
|
|
}
|
|
|
|
private void UpdateIndexBufferPattern()
|
|
{
|
|
IndexBufferPattern pattern = null;
|
|
|
|
if (Gd.TopologyUnsupported(_topology))
|
|
{
|
|
pattern = _topology switch
|
|
{
|
|
GAL.PrimitiveTopology.Quads => QuadsToTrisPattern,
|
|
GAL.PrimitiveTopology.TriangleFan or
|
|
GAL.PrimitiveTopology.Polygon => TriFanToTrisPattern,
|
|
_ => throw new NotSupportedException($"Unsupported topology: {_topology}")
|
|
};
|
|
}
|
|
|
|
if (_indexBufferPattern != pattern)
|
|
{
|
|
_indexBufferPattern = pattern;
|
|
_needsIndexBufferRebind = true;
|
|
}
|
|
}
|
|
|
|
public void DrawIndexed(int indexCount, int instanceCount, int firstIndex, int firstVertex, int firstInstance)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateIndexBufferPattern();
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
DrawCount++;
|
|
|
|
if (_indexBufferPattern != null)
|
|
{
|
|
// Convert the index buffer into a supported topology.
|
|
IndexBufferPattern pattern = _indexBufferPattern;
|
|
|
|
int convertedCount = pattern.GetConvertedCount(indexCount);
|
|
|
|
if (_needsIndexBufferRebind)
|
|
{
|
|
_indexBuffer.BindConvertedIndexBuffer(Gd, Cbs, firstIndex, indexCount, convertedCount, pattern);
|
|
|
|
_needsIndexBufferRebind = false;
|
|
}
|
|
|
|
BeginRenderPass(); // May have been interrupted to set buffer data.
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)convertedCount, (uint)instanceCount, 0, firstVertex, (uint)firstInstance);
|
|
}
|
|
else
|
|
{
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDrawIndexed(CommandBuffer, (uint)indexCount, (uint)instanceCount, (uint)firstIndex, firstVertex, (uint)firstInstance);
|
|
}
|
|
}
|
|
|
|
public void DrawIndexedIndirect(BufferRange indirectBuffer)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateIndexBufferPattern();
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
DrawCount++;
|
|
|
|
if (_indexBufferPattern != null)
|
|
{
|
|
// Convert the index buffer into a supported topology.
|
|
IndexBufferPattern pattern = _indexBufferPattern;
|
|
|
|
Auto<DisposableBuffer> indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect(
|
|
Gd,
|
|
Cbs,
|
|
indirectBuffer,
|
|
BufferRange.Empty,
|
|
pattern,
|
|
false,
|
|
1,
|
|
indirectBuffer.Size);
|
|
|
|
_needsIndexBufferRebind = false;
|
|
|
|
BeginRenderPass(); // May have been interrupted to set buffer data.
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value, 0, 1, (uint)indirectBuffer.Size);
|
|
}
|
|
else
|
|
{
|
|
var buffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
|
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
|
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
Gd.Api.CmdDrawIndexedIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
|
|
}
|
|
}
|
|
|
|
public void DrawIndexedIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UpdateIndexBufferPattern();
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
DrawCount++;
|
|
|
|
var countBuffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
|
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
|
|
|
if (_indexBufferPattern != null)
|
|
{
|
|
// Convert the index buffer into a supported topology.
|
|
IndexBufferPattern pattern = _indexBufferPattern;
|
|
|
|
Auto<DisposableBuffer> indirectBufferAuto = _indexBuffer.BindConvertedIndexBufferIndirect(
|
|
Gd,
|
|
Cbs,
|
|
indirectBuffer,
|
|
parameterBuffer,
|
|
pattern,
|
|
true,
|
|
maxDrawCount,
|
|
stride);
|
|
|
|
_needsIndexBufferRebind = false;
|
|
|
|
BeginRenderPass(); // May have been interrupted to set buffer data.
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
if (Gd.Capabilities.SupportsIndirectParameters)
|
|
{
|
|
Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
|
|
CommandBuffer,
|
|
indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value,
|
|
0,
|
|
countBuffer,
|
|
(ulong)parameterBuffer.Offset,
|
|
(uint)maxDrawCount,
|
|
(uint)stride);
|
|
}
|
|
else
|
|
{
|
|
// This is also fine because the indirect data conversion always zeros
|
|
// the entries that are past the current draw count.
|
|
|
|
Gd.Api.CmdDrawIndexedIndirect(
|
|
CommandBuffer,
|
|
indirectBufferAuto.Get(Cbs, 0, indirectBuffer.Size).Value,
|
|
0,
|
|
(uint)maxDrawCount,
|
|
(uint)stride);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
var buffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
|
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
|
|
|
ResumeTransformFeedbackInternal();
|
|
|
|
if (Gd.Capabilities.SupportsIndirectParameters)
|
|
{
|
|
Gd.DrawIndirectCountApi.CmdDrawIndexedIndirectCount(
|
|
CommandBuffer,
|
|
buffer,
|
|
(ulong)indirectBuffer.Offset,
|
|
countBuffer,
|
|
(ulong)parameterBuffer.Offset,
|
|
(uint)maxDrawCount,
|
|
(uint)stride);
|
|
}
|
|
else
|
|
{
|
|
// Not fully correct, but we can't do much better if the host does not support indirect count.
|
|
Gd.Api.CmdDrawIndexedIndirect(
|
|
CommandBuffer,
|
|
buffer,
|
|
(ulong)indirectBuffer.Offset,
|
|
(uint)maxDrawCount,
|
|
(uint)stride);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void DrawIndirect(BufferRange indirectBuffer)
|
|
{
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: Support quads and other unsupported topologies.
|
|
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
ResumeTransformFeedbackInternal();
|
|
DrawCount++;
|
|
|
|
var buffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
|
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
|
|
|
Gd.Api.CmdDrawIndirect(CommandBuffer, buffer, (ulong)indirectBuffer.Offset, 1, (uint)indirectBuffer.Size);
|
|
}
|
|
|
|
public void DrawIndirectCount(BufferRange indirectBuffer, BufferRange parameterBuffer, int maxDrawCount, int stride)
|
|
{
|
|
if (!Gd.Capabilities.SupportsIndirectParameters)
|
|
{
|
|
// TODO: Fallback for when this is not supported.
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
if (!_program.IsLinked)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: Support quads and other unsupported topologies.
|
|
|
|
RecreatePipelineIfNeeded(PipelineBindPoint.Graphics);
|
|
BeginRenderPass();
|
|
ResumeTransformFeedbackInternal();
|
|
DrawCount++;
|
|
|
|
var buffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, indirectBuffer.Handle, indirectBuffer.Offset, indirectBuffer.Size, false)
|
|
.Get(Cbs, indirectBuffer.Offset, indirectBuffer.Size).Value;
|
|
|
|
var countBuffer = Gd.BufferManager
|
|
.GetBuffer(CommandBuffer, parameterBuffer.Handle, parameterBuffer.Offset, parameterBuffer.Size, false)
|
|
.Get(Cbs, parameterBuffer.Offset, parameterBuffer.Size).Value;
|
|
|
|
Gd.DrawIndirectCountApi.CmdDrawIndirectCount(
|
|
CommandBuffer,
|
|
buffer,
|
|
(ulong)indirectBuffer.Offset,
|
|
countBuffer,
|
|
(ulong)parameterBuffer.Offset,
|
|
(uint)maxDrawCount,
|
|
(uint)stride);
|
|
}
|
|
|
|
public void DrawTexture(ITexture texture, ISampler sampler, Extents2DF srcRegion, Extents2DF dstRegion)
|
|
{
|
|
if (texture is TextureView srcTexture)
|
|
{
|
|
SupportBufferUpdater.Commit();
|
|
|
|
var oldCullMode = _newState.CullMode;
|
|
var oldStencilTestEnable = _newState.StencilTestEnable;
|
|
var oldDepthTestEnable = _newState.DepthTestEnable;
|
|
var oldDepthWriteEnable = _newState.DepthWriteEnable;
|
|
var oldTopology = _newState.Topology;
|
|
var oldViewports = DynamicState.Viewports;
|
|
var oldViewportsCount = _newState.ViewportsCount;
|
|
|
|
_newState.CullMode = CullModeFlags.None;
|
|
_newState.StencilTestEnable = false;
|
|
_newState.DepthTestEnable = false;
|
|
_newState.DepthWriteEnable = false;
|
|
SignalStateChange();
|
|
|
|
Gd.HelperShader.DrawTexture(
|
|
Gd,
|
|
this,
|
|
srcTexture,
|
|
sampler,
|
|
srcRegion,
|
|
dstRegion);
|
|
|
|
_newState.CullMode = oldCullMode;
|
|
_newState.StencilTestEnable = oldStencilTestEnable;
|
|
_newState.DepthTestEnable = oldDepthTestEnable;
|
|
_newState.DepthWriteEnable = oldDepthWriteEnable;
|
|
_newState.Topology = oldTopology;
|
|
|
|
DynamicState.SetViewports(ref oldViewports, oldViewportsCount);
|
|
|
|
_newState.ViewportsCount = oldViewportsCount;
|
|
SignalStateChange();
|
|
}
|
|
}
|
|
|
|
public void EndTransformFeedback()
|
|
{
|
|
PauseTransformFeedbackInternal();
|
|
_tfEnabled = false;
|
|
}
|
|
|
|
public double GetCounterDivisor(CounterType type)
|
|
{
|
|
if (type == CounterType.SamplesPassed)
|
|
{
|
|
return _renderScale[0].X * _renderScale[0].X;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
public bool IsCommandBufferActive(CommandBuffer cb)
|
|
{
|
|
return CommandBuffer.Handle == cb.Handle;
|
|
}
|
|
|
|
public void SetAlphaTest(bool enable, float reference, GAL.CompareOp op)
|
|
{
|
|
// This is currently handled using shader specialization, as Vulkan does not support alpha test.
|
|
// In the future, we may want to use this to write the reference value into the support buffer,
|
|
// to avoid creating one version of the shader per reference value used.
|
|
}
|
|
|
|
public void SetBlendState(AdvancedBlendDescriptor blend)
|
|
{
|
|
for (int index = 0; index < Constants.MaxRenderTargets; index++)
|
|
{
|
|
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index];
|
|
|
|
if (index == 0)
|
|
{
|
|
var blendOp = blend.Op.Convert();
|
|
|
|
vkBlend = new PipelineColorBlendAttachmentState(
|
|
blendEnable: true,
|
|
colorBlendOp: blendOp,
|
|
alphaBlendOp: blendOp,
|
|
colorWriteMask: vkBlend.ColorWriteMask);
|
|
|
|
if (Gd.Capabilities.SupportsBlendEquationAdvancedNonPreMultipliedSrcColor)
|
|
{
|
|
_newState.AdvancedBlendSrcPreMultiplied = blend.SrcPreMultiplied;
|
|
}
|
|
|
|
if (Gd.Capabilities.SupportsBlendEquationAdvancedCorrelatedOverlap)
|
|
{
|
|
_newState.AdvancedBlendOverlap = blend.Overlap.Convert();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vkBlend = new PipelineColorBlendAttachmentState(
|
|
colorWriteMask: vkBlend.ColorWriteMask);
|
|
}
|
|
|
|
if (vkBlend.ColorWriteMask == 0)
|
|
{
|
|
_storedBlend[index] = vkBlend;
|
|
|
|
vkBlend = new PipelineColorBlendAttachmentState();
|
|
}
|
|
}
|
|
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetBlendState(int index, BlendDescriptor blend)
|
|
{
|
|
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[index];
|
|
|
|
if (blend.Enable)
|
|
{
|
|
vkBlend.BlendEnable = blend.Enable;
|
|
vkBlend.SrcColorBlendFactor = blend.ColorSrcFactor.Convert();
|
|
vkBlend.DstColorBlendFactor = blend.ColorDstFactor.Convert();
|
|
vkBlend.ColorBlendOp = blend.ColorOp.Convert();
|
|
vkBlend.SrcAlphaBlendFactor = blend.AlphaSrcFactor.Convert();
|
|
vkBlend.DstAlphaBlendFactor = blend.AlphaDstFactor.Convert();
|
|
vkBlend.AlphaBlendOp = blend.AlphaOp.Convert();
|
|
}
|
|
else
|
|
{
|
|
vkBlend = new PipelineColorBlendAttachmentState(
|
|
colorWriteMask: vkBlend.ColorWriteMask);
|
|
}
|
|
|
|
if (vkBlend.ColorWriteMask == 0)
|
|
{
|
|
_storedBlend[index] = vkBlend;
|
|
|
|
vkBlend = new PipelineColorBlendAttachmentState();
|
|
}
|
|
|
|
DynamicState.SetBlendConstants(
|
|
blend.BlendConstant.Red,
|
|
blend.BlendConstant.Green,
|
|
blend.BlendConstant.Blue,
|
|
blend.BlendConstant.Alpha);
|
|
|
|
// Reset advanced blend state back defaults to the cache to help the pipeline cache.
|
|
_newState.AdvancedBlendSrcPreMultiplied = true;
|
|
_newState.AdvancedBlendDstPreMultiplied = true;
|
|
_newState.AdvancedBlendOverlap = BlendOverlapEXT.UncorrelatedExt;
|
|
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp)
|
|
{
|
|
DynamicState.SetDepthBias(factor, units, clamp);
|
|
|
|
_newState.DepthBiasEnable = enables != 0;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetDepthClamp(bool clamp)
|
|
{
|
|
_newState.DepthClampEnable = clamp;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetDepthMode(DepthMode mode)
|
|
{
|
|
// Currently this is emulated on the shader, because Vulkan had no support for changing the depth mode.
|
|
// In the future, we may want to use the VK_EXT_depth_clip_control extension to change it here.
|
|
}
|
|
|
|
public void SetDepthTest(DepthTestDescriptor depthTest)
|
|
{
|
|
_newState.DepthTestEnable = depthTest.TestEnable;
|
|
_newState.DepthWriteEnable = depthTest.WriteEnable;
|
|
_newState.DepthCompareOp = depthTest.Func.Convert();
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetFaceCulling(bool enable, Face face)
|
|
{
|
|
_newState.CullMode = enable ? face.Convert() : CullModeFlags.None;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetFrontFace(GAL.FrontFace frontFace)
|
|
{
|
|
_newState.FrontFace = frontFace.Convert();
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetImage(int binding, ITexture image, GAL.Format imageFormat)
|
|
{
|
|
_descriptorSetUpdater.SetImage(binding, image, imageFormat);
|
|
}
|
|
|
|
public void SetImage(int binding, Auto<DisposableImageView> image)
|
|
{
|
|
_descriptorSetUpdater.SetImage(binding, image);
|
|
}
|
|
|
|
public void SetIndexBuffer(BufferRange buffer, GAL.IndexType type)
|
|
{
|
|
if (buffer.Handle != BufferHandle.Null)
|
|
{
|
|
_indexBuffer = new IndexBufferState(buffer.Handle, buffer.Offset, buffer.Size, type.Convert());
|
|
}
|
|
else
|
|
{
|
|
_indexBuffer = IndexBufferState.Null;
|
|
}
|
|
|
|
_needsIndexBufferRebind = true;
|
|
}
|
|
|
|
public void SetLineParameters(float width, bool smooth)
|
|
{
|
|
_newState.LineWidth = width;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetLogicOpState(bool enable, LogicalOp op)
|
|
{
|
|
_newState.LogicOpEnable = enable;
|
|
_newState.LogicOp = op.Convert();
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetMultisampleState(MultisampleDescriptor multisample)
|
|
{
|
|
_newState.AlphaToCoverageEnable = multisample.AlphaToCoverageEnable;
|
|
_newState.AlphaToOneEnable = multisample.AlphaToOneEnable;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetOrigin(Origin origin)
|
|
{
|
|
// TODO.
|
|
}
|
|
|
|
public unsafe void SetPatchParameters(int vertices, ReadOnlySpan<float> defaultOuterLevel, ReadOnlySpan<float> defaultInnerLevel)
|
|
{
|
|
_newState.PatchControlPoints = (uint)vertices;
|
|
SignalStateChange();
|
|
|
|
// TODO: Default levels (likely needs emulation on shaders?)
|
|
}
|
|
|
|
public void SetPointParameters(float size, bool isProgramPointSize, bool enablePointSprite, Origin origin)
|
|
{
|
|
// TODO.
|
|
}
|
|
|
|
public void SetPolygonMode(GAL.PolygonMode frontMode, GAL.PolygonMode backMode)
|
|
{
|
|
// TODO.
|
|
}
|
|
|
|
public void SetPrimitiveRestart(bool enable, int index)
|
|
{
|
|
_newState.PrimitiveRestartEnable = enable;
|
|
// TODO: What to do about the index?
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetPrimitiveTopology(GAL.PrimitiveTopology topology)
|
|
{
|
|
_topology = topology;
|
|
|
|
var vkTopology = Gd.TopologyRemap(topology).Convert();
|
|
|
|
_newState.Topology = vkTopology;
|
|
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetProgram(IProgram program)
|
|
{
|
|
var internalProgram = (ShaderCollection)program;
|
|
var stages = internalProgram.GetInfos();
|
|
|
|
_program = internalProgram;
|
|
|
|
_descriptorSetUpdater.SetProgram(internalProgram);
|
|
|
|
_newState.PipelineLayout = internalProgram.PipelineLayout;
|
|
_newState.StagesCount = (uint)stages.Length;
|
|
|
|
stages.CopyTo(_newState.Stages.AsSpan().Slice(0, stages.Length));
|
|
|
|
SignalStateChange();
|
|
|
|
if (_program.IsCompute)
|
|
{
|
|
EndRenderPass();
|
|
}
|
|
}
|
|
|
|
public void Specialize<T>(in T data) where T : unmanaged
|
|
{
|
|
var dataSpan = MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.AsRef(in data), 1));
|
|
|
|
if (!dataSpan.SequenceEqual(_newState.SpecializationData.Span))
|
|
{
|
|
_newState.SpecializationData = new SpecData(dataSpan);
|
|
|
|
SignalStateChange();
|
|
}
|
|
}
|
|
|
|
protected virtual void SignalAttachmentChange()
|
|
{
|
|
}
|
|
|
|
public void SetRasterizerDiscard(bool discard)
|
|
{
|
|
_newState.RasterizerDiscardEnable = discard;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetRenderTargetColorMasks(ReadOnlySpan<uint> componentMask)
|
|
{
|
|
int count = Math.Min(Constants.MaxRenderTargets, componentMask.Length);
|
|
int writtenAttachments = 0;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
|
|
var newMask = (ColorComponentFlags)componentMask[i];
|
|
|
|
// When color write mask is 0, remove all blend state to help the pipeline cache.
|
|
// Restore it when the mask becomes non-zero.
|
|
if (vkBlend.ColorWriteMask != newMask)
|
|
{
|
|
if (newMask == 0)
|
|
{
|
|
_storedBlend[i] = vkBlend;
|
|
|
|
vkBlend = new PipelineColorBlendAttachmentState();
|
|
}
|
|
else if (vkBlend.ColorWriteMask == 0)
|
|
{
|
|
vkBlend = _storedBlend[i];
|
|
}
|
|
}
|
|
|
|
vkBlend.ColorWriteMask = newMask;
|
|
|
|
if (componentMask[i] != 0)
|
|
{
|
|
writtenAttachments++;
|
|
}
|
|
}
|
|
|
|
if (_framebufferUsingColorWriteMask)
|
|
{
|
|
SetRenderTargetsInternal(_preMaskColors, _preMaskDepthStencil, true);
|
|
}
|
|
else
|
|
{
|
|
SignalStateChange();
|
|
|
|
if (writtenAttachments != _writtenAttachmentCount)
|
|
{
|
|
SignalAttachmentChange();
|
|
_writtenAttachmentCount = writtenAttachments;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void SetRenderTargetsInternal(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
|
{
|
|
FramebufferParams?.UpdateModifications();
|
|
CreateFramebuffer(colors, depthStencil, filterWriteMasked);
|
|
CreateRenderPass();
|
|
SignalStateChange();
|
|
SignalAttachmentChange();
|
|
}
|
|
|
|
public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
|
|
{
|
|
_framebufferUsingColorWriteMask = false;
|
|
SetRenderTargetsInternal(colors, depthStencil, Gd.IsTBDR);
|
|
}
|
|
|
|
public void SetRenderTargetScale(float scale)
|
|
{
|
|
_renderScale[0].X = scale;
|
|
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, 1); // Just the first element.
|
|
}
|
|
|
|
public void SetScissors(ReadOnlySpan<Rectangle<int>> regions)
|
|
{
|
|
int maxScissors = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
|
|
int count = Math.Min(maxScissors, regions.Length);
|
|
if (count > 0)
|
|
{
|
|
ClearScissor = regions[0];
|
|
}
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var region = regions[i];
|
|
var offset = new Offset2D(region.X, region.Y);
|
|
var extent = new Extent2D((uint)region.Width, (uint)region.Height);
|
|
|
|
DynamicState.SetScissor(i, new Rect2D(offset, extent));
|
|
}
|
|
|
|
DynamicState.ScissorsCount = count;
|
|
|
|
_newState.ScissorsCount = (uint)count;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetStencilTest(StencilTestDescriptor stencilTest)
|
|
{
|
|
DynamicState.SetStencilMasks(
|
|
(uint)stencilTest.BackFuncMask,
|
|
(uint)stencilTest.BackMask,
|
|
(uint)stencilTest.BackFuncRef,
|
|
(uint)stencilTest.FrontFuncMask,
|
|
(uint)stencilTest.FrontMask,
|
|
(uint)stencilTest.FrontFuncRef);
|
|
|
|
_newState.StencilTestEnable = stencilTest.TestEnable;
|
|
_newState.StencilBackFailOp = stencilTest.BackSFail.Convert();
|
|
_newState.StencilBackPassOp = stencilTest.BackDpPass.Convert();
|
|
_newState.StencilBackDepthFailOp = stencilTest.BackDpFail.Convert();
|
|
_newState.StencilBackCompareOp = stencilTest.BackFunc.Convert();
|
|
_newState.StencilFrontFailOp = stencilTest.FrontSFail.Convert();
|
|
_newState.StencilFrontPassOp = stencilTest.FrontDpPass.Convert();
|
|
_newState.StencilFrontDepthFailOp = stencilTest.FrontDpFail.Convert();
|
|
_newState.StencilFrontCompareOp = stencilTest.FrontFunc.Convert();
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetStorageBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
|
{
|
|
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, buffers);
|
|
}
|
|
|
|
public void SetStorageBuffers(int first, ReadOnlySpan<Auto<DisposableBuffer>> buffers)
|
|
{
|
|
_descriptorSetUpdater.SetStorageBuffers(CommandBuffer, first, buffers);
|
|
}
|
|
|
|
public void SetTextureAndSampler(ShaderStage stage, int binding, ITexture texture, ISampler sampler)
|
|
{
|
|
_descriptorSetUpdater.SetTextureAndSampler(Cbs, stage, binding, texture, sampler);
|
|
}
|
|
|
|
public void SetTransformFeedbackBuffers(ReadOnlySpan<BufferRange> buffers)
|
|
{
|
|
PauseTransformFeedbackInternal();
|
|
|
|
int count = Math.Min(Constants.MaxTransformFeedbackBuffers, buffers.Length);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var range = buffers[i];
|
|
|
|
_transformFeedbackBuffers[i].Dispose();
|
|
|
|
if (range.Handle != BufferHandle.Null)
|
|
{
|
|
_transformFeedbackBuffers[i] =
|
|
new BufferState(Gd.BufferManager.GetBuffer(CommandBuffer, range.Handle, range.Offset, range.Size, true), range.Offset, range.Size);
|
|
_transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
|
|
}
|
|
else
|
|
{
|
|
_transformFeedbackBuffers[i] = BufferState.Null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void SetUniformBuffers(ReadOnlySpan<BufferAssignment> buffers)
|
|
{
|
|
_descriptorSetUpdater.SetUniformBuffers(CommandBuffer, buffers);
|
|
}
|
|
|
|
public void SetUserClipDistance(int index, bool enableClip)
|
|
{
|
|
// TODO.
|
|
}
|
|
|
|
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
|
{
|
|
var formatCapabilities = Gd.FormatCapabilities;
|
|
|
|
Span<int> newVbScalarSizes = stackalloc int[Constants.MaxVertexBuffers];
|
|
|
|
int count = Math.Min(Constants.MaxVertexAttributes, vertexAttribs.Length);
|
|
uint dirtyVbSizes = 0;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var attribute = vertexAttribs[i];
|
|
var rawIndex = attribute.BufferIndex;
|
|
var bufferIndex = attribute.IsZero ? 0 : rawIndex + 1;
|
|
|
|
if (!attribute.IsZero)
|
|
{
|
|
newVbScalarSizes[rawIndex] = Math.Max(newVbScalarSizes[rawIndex], attribute.Format.GetScalarSize());
|
|
dirtyVbSizes |= 1u << rawIndex;
|
|
}
|
|
|
|
_newState.Internal.VertexAttributeDescriptions[i] = new VertexInputAttributeDescription(
|
|
(uint)i,
|
|
(uint)bufferIndex,
|
|
formatCapabilities.ConvertToVertexVkFormat(attribute.Format),
|
|
(uint)attribute.Offset);
|
|
}
|
|
|
|
while (dirtyVbSizes != 0)
|
|
{
|
|
int dirtyBit = BitOperations.TrailingZeroCount(dirtyVbSizes);
|
|
|
|
ref var buffer = ref _vertexBuffers[dirtyBit + 1];
|
|
|
|
if (buffer.AttributeScalarAlignment != newVbScalarSizes[dirtyBit])
|
|
{
|
|
_vertexBuffersDirty |= 1UL << (dirtyBit + 1);
|
|
buffer.AttributeScalarAlignment = newVbScalarSizes[dirtyBit];
|
|
}
|
|
|
|
dirtyVbSizes &= ~(1u << dirtyBit);
|
|
}
|
|
|
|
_newState.VertexAttributeDescriptionsCount = (uint)count;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetVertexBuffers(ReadOnlySpan<VertexBufferDescriptor> vertexBuffers)
|
|
{
|
|
int count = Math.Min(Constants.MaxVertexBuffers, vertexBuffers.Length);
|
|
|
|
_newState.Internal.VertexBindingDescriptions[0] = new VertexInputBindingDescription(0, 0, VertexInputRate.Vertex);
|
|
|
|
int validCount = 1;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var vertexBuffer = vertexBuffers[i];
|
|
|
|
// TODO: Support divisor > 1
|
|
var inputRate = vertexBuffer.Divisor != 0 ? VertexInputRate.Instance : VertexInputRate.Vertex;
|
|
|
|
if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
|
|
{
|
|
var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
|
|
if (vb != null)
|
|
{
|
|
int binding = i + 1;
|
|
int descriptorIndex = validCount++;
|
|
|
|
_newState.Internal.VertexBindingDescriptions[descriptorIndex] = new VertexInputBindingDescription(
|
|
(uint)binding,
|
|
(uint)vertexBuffer.Stride,
|
|
inputRate);
|
|
|
|
int vbSize = vertexBuffer.Buffer.Size;
|
|
|
|
if (Gd.Vendor == Vendor.Amd && !Gd.IsMoltenVk && vertexBuffer.Stride > 0)
|
|
{
|
|
// AMD has a bug where if offset + stride * count is greater than
|
|
// the size, then the last attribute will have the wrong value.
|
|
// As a workaround, simply use the full buffer size.
|
|
int remainder = vbSize % vertexBuffer.Stride;
|
|
if (remainder != 0)
|
|
{
|
|
vbSize += vertexBuffer.Stride - remainder;
|
|
}
|
|
}
|
|
|
|
ref var buffer = ref _vertexBuffers[binding];
|
|
int oldScalarAlign = buffer.AttributeScalarAlignment;
|
|
|
|
buffer.Dispose();
|
|
|
|
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
|
|
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
|
{
|
|
buffer = new VertexBufferState(
|
|
vb,
|
|
descriptorIndex,
|
|
vertexBuffer.Buffer.Offset,
|
|
vbSize,
|
|
vertexBuffer.Stride);
|
|
|
|
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState);
|
|
}
|
|
else
|
|
{
|
|
// May need to be rewritten. Bind this buffer before draw.
|
|
|
|
buffer = new VertexBufferState(
|
|
vertexBuffer.Buffer.Handle,
|
|
descriptorIndex,
|
|
vertexBuffer.Buffer.Offset,
|
|
vbSize,
|
|
vertexBuffer.Stride);
|
|
|
|
_vertexBuffersDirty |= 1UL << binding;
|
|
}
|
|
|
|
buffer.AttributeScalarAlignment = oldScalarAlign;
|
|
}
|
|
}
|
|
}
|
|
|
|
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SetViewports(ReadOnlySpan<GAL.Viewport> viewports, bool disableTransform)
|
|
{
|
|
int maxViewports = Gd.Capabilities.SupportsMultiView ? Constants.MaxViewports : 1;
|
|
int count = Math.Min(maxViewports, viewports.Length);
|
|
|
|
static float Clamp(float value)
|
|
{
|
|
return Math.Clamp(value, 0f, 1f);
|
|
}
|
|
|
|
DynamicState.ViewportsCount = (uint)count;
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
var viewport = viewports[i];
|
|
|
|
DynamicState.SetViewport(i, new Silk.NET.Vulkan.Viewport(
|
|
viewport.Region.X,
|
|
viewport.Region.Y,
|
|
viewport.Region.Width == 0f ? 1f : viewport.Region.Width,
|
|
viewport.Region.Height == 0f ? 1f : viewport.Region.Height,
|
|
Clamp(viewport.DepthNear),
|
|
Clamp(viewport.DepthFar)));
|
|
}
|
|
|
|
float disableTransformF = disableTransform ? 1.0f : 0.0f;
|
|
if (SupportBufferUpdater.Data.ViewportInverse.W != disableTransformF || disableTransform)
|
|
{
|
|
float scale = _renderScale[0].X;
|
|
SupportBufferUpdater.UpdateViewportInverse(new Vector4<float>
|
|
{
|
|
X = scale * 2f / viewports[0].Region.Width,
|
|
Y = scale * 2f / viewports[0].Region.Height,
|
|
Z = 1,
|
|
W = disableTransformF
|
|
});
|
|
}
|
|
|
|
_newState.ViewportsCount = (uint)count;
|
|
SignalStateChange();
|
|
}
|
|
|
|
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
|
{
|
|
_indexBuffer.Swap(from, to);
|
|
|
|
for (int i = 0; i < _vertexBuffers.Length; i++)
|
|
{
|
|
_vertexBuffers[i].Swap(from, to);
|
|
}
|
|
|
|
for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
|
|
{
|
|
_transformFeedbackBuffers[i].Swap(from, to);
|
|
}
|
|
|
|
_descriptorSetUpdater.SwapBuffer(from, to);
|
|
|
|
SignalCommandBufferChange();
|
|
}
|
|
|
|
public unsafe void TextureBarrier()
|
|
{
|
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
|
{
|
|
SType = StructureType.MemoryBarrier,
|
|
SrcAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit,
|
|
DstAccessMask = AccessFlags.MemoryReadBit | AccessFlags.MemoryWriteBit
|
|
};
|
|
|
|
Gd.Api.CmdPipelineBarrier(
|
|
CommandBuffer,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
PipelineStageFlags.FragmentShaderBit,
|
|
0,
|
|
1,
|
|
memoryBarrier,
|
|
0,
|
|
null,
|
|
0,
|
|
null);
|
|
}
|
|
|
|
public void TextureBarrierTiled()
|
|
{
|
|
TextureBarrier();
|
|
}
|
|
|
|
public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
|
|
{
|
|
bool changed = false;
|
|
|
|
for (int index = 0; index < totalCount; index++)
|
|
{
|
|
if (_renderScale[1 + index].X != scales[index])
|
|
{
|
|
_renderScale[1 + index].X = scales[index];
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
// Only update fragment count if there are scales after it for the vertex stage.
|
|
if (fragmentCount != totalCount && fragmentCount != _fragmentScaleCount)
|
|
{
|
|
_fragmentScaleCount = fragmentCount;
|
|
SupportBufferUpdater.UpdateFragmentRenderScaleCount(_fragmentScaleCount);
|
|
}
|
|
|
|
if (changed)
|
|
{
|
|
SupportBufferUpdater.UpdateRenderScale(_renderScale, 0, 1 + totalCount);
|
|
}
|
|
}
|
|
|
|
protected void SignalCommandBufferChange()
|
|
{
|
|
_needsIndexBufferRebind = true;
|
|
_needsTransformFeedbackBuffersRebind = true;
|
|
_vertexBuffersDirty = ulong.MaxValue >> (64 - _vertexBuffers.Length);
|
|
|
|
_descriptorSetUpdater.SignalCommandBufferChange();
|
|
DynamicState.ForceAllDirty();
|
|
_currentPipelineHandle = 0;
|
|
}
|
|
|
|
private void CreateFramebuffer(ITexture[] colors, ITexture depthStencil, bool filterWriteMasked)
|
|
{
|
|
if (filterWriteMasked)
|
|
{
|
|
// TBDR GPUs don't work properly if the same attachment is bound to multiple targets,
|
|
// due to each attachment being a copy of the real attachment, rather than a direct write.
|
|
|
|
// Just try to remove duplicate attachments.
|
|
// Save a copy of the array to rebind when mask changes.
|
|
|
|
void maskOut()
|
|
{
|
|
if (!_framebufferUsingColorWriteMask)
|
|
{
|
|
_preMaskColors = colors.ToArray();
|
|
_preMaskDepthStencil = depthStencil;
|
|
}
|
|
|
|
// If true, then the framebuffer must be recreated when the mask changes.
|
|
_framebufferUsingColorWriteMask = true;
|
|
}
|
|
|
|
// Look for textures that are masked out.
|
|
|
|
for (int i = 0; i < colors.Length; i++)
|
|
{
|
|
if (colors[i] == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ref var vkBlend = ref _newState.Internal.ColorBlendAttachmentState[i];
|
|
|
|
for (int j = 0; j < i; j++)
|
|
{
|
|
// Check each binding for a duplicate binding before it.
|
|
|
|
if (colors[i] == colors[j])
|
|
{
|
|
// Prefer the binding with no write mask.
|
|
ref var vkBlend2 = ref _newState.Internal.ColorBlendAttachmentState[j];
|
|
if (vkBlend.ColorWriteMask == 0)
|
|
{
|
|
colors[i] = null;
|
|
maskOut();
|
|
}
|
|
else if (vkBlend2.ColorWriteMask == 0)
|
|
{
|
|
colors[j] = null;
|
|
maskOut();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FramebufferParams = new FramebufferParams(Device, colors, depthStencil);
|
|
UpdatePipelineAttachmentFormats();
|
|
}
|
|
|
|
protected void UpdatePipelineAttachmentFormats()
|
|
{
|
|
var dstAttachmentFormats = _newState.Internal.AttachmentFormats.AsSpan();
|
|
FramebufferParams.AttachmentFormats.CopyTo(dstAttachmentFormats);
|
|
|
|
for (int i = FramebufferParams.AttachmentFormats.Length; i < dstAttachmentFormats.Length; i++)
|
|
{
|
|
dstAttachmentFormats[i] = 0;
|
|
}
|
|
|
|
_newState.ColorBlendAttachmentStateCount = (uint)(FramebufferParams.MaxColorAttachmentIndex + 1);
|
|
_newState.HasDepthStencil = FramebufferParams.HasDepthStencil;
|
|
_newState.SamplesCount = FramebufferParams.AttachmentSamples.Length != 0 ? FramebufferParams.AttachmentSamples[0] : 1;
|
|
}
|
|
|
|
protected unsafe void CreateRenderPass()
|
|
{
|
|
const int MaxAttachments = Constants.MaxRenderTargets + 1;
|
|
|
|
AttachmentDescription[] attachmentDescs = null;
|
|
|
|
var subpass = new SubpassDescription()
|
|
{
|
|
PipelineBindPoint = PipelineBindPoint.Graphics
|
|
};
|
|
|
|
AttachmentReference* attachmentReferences = stackalloc AttachmentReference[MaxAttachments];
|
|
|
|
var hasFramebuffer = FramebufferParams != null;
|
|
|
|
if (hasFramebuffer && FramebufferParams.AttachmentsCount != 0)
|
|
{
|
|
attachmentDescs = new AttachmentDescription[FramebufferParams.AttachmentsCount];
|
|
|
|
for (int i = 0; i < FramebufferParams.AttachmentsCount; i++)
|
|
{
|
|
attachmentDescs[i] = new AttachmentDescription(
|
|
0,
|
|
FramebufferParams.AttachmentFormats[i],
|
|
TextureStorage.ConvertToSampleCountFlags(Gd.Capabilities.SupportedSampleCounts, FramebufferParams.AttachmentSamples[i]),
|
|
AttachmentLoadOp.Load,
|
|
AttachmentStoreOp.Store,
|
|
AttachmentLoadOp.Load,
|
|
AttachmentStoreOp.Store,
|
|
ImageLayout.General,
|
|
ImageLayout.General);
|
|
}
|
|
|
|
int colorAttachmentsCount = FramebufferParams.ColorAttachmentsCount;
|
|
|
|
if (colorAttachmentsCount > MaxAttachments - 1)
|
|
{
|
|
colorAttachmentsCount = MaxAttachments - 1;
|
|
}
|
|
|
|
if (colorAttachmentsCount != 0)
|
|
{
|
|
int maxAttachmentIndex = FramebufferParams.MaxColorAttachmentIndex;
|
|
subpass.ColorAttachmentCount = (uint)maxAttachmentIndex + 1;
|
|
subpass.PColorAttachments = &attachmentReferences[0];
|
|
|
|
// Fill with VK_ATTACHMENT_UNUSED to cover any gaps.
|
|
for (int i = 0; i <= maxAttachmentIndex; i++)
|
|
{
|
|
subpass.PColorAttachments[i] = new AttachmentReference(Vk.AttachmentUnused, ImageLayout.Undefined);
|
|
}
|
|
|
|
for (int i = 0; i < colorAttachmentsCount; i++)
|
|
{
|
|
int bindIndex = FramebufferParams.AttachmentIndices[i];
|
|
|
|
subpass.PColorAttachments[bindIndex] = new AttachmentReference((uint)i, ImageLayout.General);
|
|
}
|
|
}
|
|
|
|
if (FramebufferParams.HasDepthStencil)
|
|
{
|
|
uint dsIndex = (uint)FramebufferParams.AttachmentsCount - 1;
|
|
|
|
subpass.PDepthStencilAttachment = &attachmentReferences[MaxAttachments - 1];
|
|
*subpass.PDepthStencilAttachment = new AttachmentReference(dsIndex, ImageLayout.General);
|
|
}
|
|
}
|
|
|
|
var subpassDependency = PipelineConverter.CreateSubpassDependency();
|
|
|
|
fixed (AttachmentDescription* pAttachmentDescs = attachmentDescs)
|
|
{
|
|
var renderPassCreateInfo = new RenderPassCreateInfo()
|
|
{
|
|
SType = StructureType.RenderPassCreateInfo,
|
|
PAttachments = pAttachmentDescs,
|
|
AttachmentCount = attachmentDescs != null ? (uint)attachmentDescs.Length : 0,
|
|
PSubpasses = &subpass,
|
|
SubpassCount = 1,
|
|
PDependencies = &subpassDependency,
|
|
DependencyCount = 1
|
|
};
|
|
|
|
Gd.Api.CreateRenderPass(Device, renderPassCreateInfo, null, out var renderPass).ThrowOnError();
|
|
|
|
_renderPass?.Dispose();
|
|
_renderPass = new Auto<DisposableRenderPass>(new DisposableRenderPass(Gd.Api, Device, renderPass));
|
|
}
|
|
|
|
EndRenderPass();
|
|
|
|
_framebuffer?.Dispose();
|
|
_framebuffer = hasFramebuffer ? FramebufferParams.Create(Gd.Api, Cbs, _renderPass) : null;
|
|
}
|
|
|
|
protected void SignalStateChange()
|
|
{
|
|
_stateDirty = true;
|
|
}
|
|
|
|
private void RecreatePipelineIfNeeded(PipelineBindPoint pbp)
|
|
{
|
|
if (AutoFlush.ShouldFlushDraw(DrawCount))
|
|
{
|
|
Gd.FlushAllCommands();
|
|
}
|
|
|
|
DynamicState.ReplayIfDirty(Gd.Api, CommandBuffer);
|
|
|
|
// Commit changes to the support buffer before drawing.
|
|
SupportBufferUpdater.Commit();
|
|
|
|
if (_needsIndexBufferRebind && _indexBufferPattern == null)
|
|
{
|
|
_indexBuffer.BindIndexBuffer(Gd, Cbs);
|
|
_needsIndexBufferRebind = false;
|
|
}
|
|
|
|
if (_needsTransformFeedbackBuffersRebind)
|
|
{
|
|
PauseTransformFeedbackInternal();
|
|
|
|
for (int i = 0; i < Constants.MaxTransformFeedbackBuffers; i++)
|
|
{
|
|
_transformFeedbackBuffers[i].BindTransformFeedbackBuffer(Gd, Cbs, (uint)i);
|
|
}
|
|
|
|
_needsTransformFeedbackBuffersRebind = false;
|
|
}
|
|
|
|
if (_vertexBuffersDirty != 0)
|
|
{
|
|
while (_vertexBuffersDirty != 0)
|
|
{
|
|
int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
|
|
|
|
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState);
|
|
|
|
_vertexBuffersDirty &= ~(1UL << i);
|
|
}
|
|
}
|
|
|
|
if (_stateDirty || Pbp != pbp)
|
|
{
|
|
CreatePipeline(pbp);
|
|
_stateDirty = false;
|
|
Pbp = pbp;
|
|
}
|
|
|
|
_descriptorSetUpdater.UpdateAndBindDescriptorSets(Cbs, pbp);
|
|
}
|
|
|
|
private void CreatePipeline(PipelineBindPoint pbp)
|
|
{
|
|
// We can only create a pipeline if the have the shader stages set.
|
|
if (_newState.Stages != null)
|
|
{
|
|
if (pbp == PipelineBindPoint.Graphics && _renderPass == null)
|
|
{
|
|
CreateRenderPass();
|
|
}
|
|
|
|
var pipeline = pbp == PipelineBindPoint.Compute
|
|
? _newState.CreateComputePipeline(Gd, Device, _program, PipelineCache)
|
|
: _newState.CreateGraphicsPipeline(Gd, Device, _program, PipelineCache, _renderPass.Get(Cbs).Value);
|
|
|
|
ulong pipelineHandle = pipeline.GetUnsafe().Value.Handle;
|
|
|
|
if (_currentPipelineHandle != pipelineHandle)
|
|
{
|
|
_currentPipelineHandle = pipelineHandle;
|
|
Pipeline = pipeline;
|
|
|
|
PauseTransformFeedbackInternal();
|
|
Gd.Api.CmdBindPipeline(CommandBuffer, pbp, Pipeline.Get(Cbs).Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
private unsafe void BeginRenderPass()
|
|
{
|
|
if (!RenderPassActive)
|
|
{
|
|
var renderArea = new Rect2D(null, new Extent2D(FramebufferParams.Width, FramebufferParams.Height));
|
|
var clearValue = new ClearValue();
|
|
|
|
var renderPassBeginInfo = new RenderPassBeginInfo()
|
|
{
|
|
SType = StructureType.RenderPassBeginInfo,
|
|
RenderPass = _renderPass.Get(Cbs).Value,
|
|
Framebuffer = _framebuffer.Get(Cbs).Value,
|
|
RenderArea = renderArea,
|
|
PClearValues = &clearValue,
|
|
ClearValueCount = 1
|
|
};
|
|
|
|
Gd.Api.CmdBeginRenderPass(CommandBuffer, renderPassBeginInfo, SubpassContents.Inline);
|
|
RenderPassActive = true;
|
|
}
|
|
}
|
|
|
|
public void EndRenderPass()
|
|
{
|
|
if (RenderPassActive)
|
|
{
|
|
PauseTransformFeedbackInternal();
|
|
Gd.Api.CmdEndRenderPass(CommandBuffer);
|
|
SignalRenderPassEnd();
|
|
RenderPassActive = false;
|
|
}
|
|
}
|
|
|
|
protected virtual void SignalRenderPassEnd()
|
|
{
|
|
}
|
|
|
|
private void PauseTransformFeedbackInternal()
|
|
{
|
|
if (_tfEnabled && _tfActive)
|
|
{
|
|
EndTransformFeedbackInternal();
|
|
_tfActive = false;
|
|
}
|
|
}
|
|
|
|
private void ResumeTransformFeedbackInternal()
|
|
{
|
|
if (_tfEnabled && !_tfActive)
|
|
{
|
|
BeginTransformFeedbackInternal();
|
|
_tfActive = true;
|
|
}
|
|
}
|
|
|
|
private unsafe void BeginTransformFeedbackInternal()
|
|
{
|
|
Gd.TransformFeedbackApi.CmdBeginTransformFeedback(CommandBuffer, 0, 0, null, null);
|
|
}
|
|
|
|
private unsafe void EndTransformFeedbackInternal()
|
|
{
|
|
Gd.TransformFeedbackApi.CmdEndTransformFeedback(CommandBuffer, 0, 0, null, null);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_renderPass?.Dispose();
|
|
_framebuffer?.Dispose();
|
|
_newState.Dispose();
|
|
_descriptorSetUpdater.Dispose();
|
|
|
|
for (int i = 0; i < _vertexBuffers.Length; i++)
|
|
{
|
|
_vertexBuffers[i].Dispose();
|
|
}
|
|
|
|
for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
|
|
{
|
|
_transformFeedbackBuffers[i].Dispose();
|
|
}
|
|
|
|
Pipeline?.Dispose();
|
|
|
|
unsafe
|
|
{
|
|
Gd.Api.DestroyPipelineCache(Device, PipelineCache, null);
|
|
}
|
|
|
|
SupportBufferUpdater.Dispose();
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
}
|
|
}
|
|
}
|