Vulkan: Batch vertex buffer updates (#4843)
* Vulkan: Batch vertex buffer updates Some games can bind a large number of vertex buffers for draws. This PR allows for vertex buffers to be updated with one call rather than one per buffer. This mostly affects the AMD Mesa driver, the testing platform was Steam Deck with Super Mario Odyssey. It was taking about 12% before, should be greatly reduced now. A small optimization has been added to avoid looking up the same buffer multiple times, as a common pattern is for the same buffer to be bound many times in a row with different ranges. * Only rebind vertex buffers if they have changed * Address feedback
This commit is contained in:
parent
0e06aace45
commit
895d9b53bc
3 changed files with 124 additions and 42 deletions
|
@ -66,6 +66,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
private ulong _vertexBuffersDirty;
|
private ulong _vertexBuffersDirty;
|
||||||
protected Rectangle<int> ClearScissor;
|
protected Rectangle<int> ClearScissor;
|
||||||
|
|
||||||
|
private readonly VertexBufferUpdater _vertexBufferUpdater;
|
||||||
|
|
||||||
public SupportBufferUpdater SupportBufferUpdater;
|
public SupportBufferUpdater SupportBufferUpdater;
|
||||||
public IndexBufferPattern QuadsToTrisPattern;
|
public IndexBufferPattern QuadsToTrisPattern;
|
||||||
public IndexBufferPattern TriFanToTrisPattern;
|
public IndexBufferPattern TriFanToTrisPattern;
|
||||||
|
@ -96,6 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
gd.Api.CreatePipelineCache(device, pipelineCacheCreateInfo, null, out PipelineCache).ThrowOnError();
|
||||||
|
|
||||||
_descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
|
_descriptorSetUpdater = new DescriptorSetUpdater(gd, this);
|
||||||
|
_vertexBufferUpdater = new VertexBufferUpdater(gd);
|
||||||
|
|
||||||
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
_transformFeedbackBuffers = new BufferState[Constants.MaxTransformFeedbackBuffers];
|
||||||
_vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
|
_vertexBuffers = new VertexBufferState[Constants.MaxVertexBuffers + 1];
|
||||||
|
@ -1185,6 +1188,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
int validCount = 1;
|
int validCount = 1;
|
||||||
|
|
||||||
|
BufferHandle lastHandle = default;
|
||||||
|
Auto<DisposableBuffer> lastBuffer = default;
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
var vertexBuffer = vertexBuffers[i];
|
var vertexBuffer = vertexBuffers[i];
|
||||||
|
@ -1194,7 +1200,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
|
if (vertexBuffer.Buffer.Handle != BufferHandle.Null)
|
||||||
{
|
{
|
||||||
var vb = Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
|
Auto<DisposableBuffer> vb = (vertexBuffer.Buffer.Handle == lastHandle) ? lastBuffer :
|
||||||
|
Gd.BufferManager.GetBuffer(CommandBuffer, vertexBuffer.Buffer.Handle, false);
|
||||||
|
|
||||||
|
lastHandle = vertexBuffer.Buffer.Handle;
|
||||||
|
lastBuffer = vb;
|
||||||
|
|
||||||
if (vb != null)
|
if (vb != null)
|
||||||
{
|
{
|
||||||
int binding = i + 1;
|
int binding = i + 1;
|
||||||
|
@ -1222,11 +1233,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
ref var buffer = ref _vertexBuffers[binding];
|
ref var buffer = ref _vertexBuffers[binding];
|
||||||
int oldScalarAlign = buffer.AttributeScalarAlignment;
|
int oldScalarAlign = buffer.AttributeScalarAlignment;
|
||||||
|
|
||||||
buffer.Dispose();
|
|
||||||
|
|
||||||
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
|
if (Gd.Capabilities.VertexBufferAlignment < 2 &&
|
||||||
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
(vertexBuffer.Stride % FormatExtensions.MaxBufferFormatScalarSize) == 0)
|
||||||
{
|
{
|
||||||
|
if (!buffer.Matches(vb, descriptorIndex, vertexBuffer.Buffer.Offset, vbSize, vertexBuffer.Stride))
|
||||||
|
{
|
||||||
|
buffer.Dispose();
|
||||||
|
|
||||||
buffer = new VertexBufferState(
|
buffer = new VertexBufferState(
|
||||||
vb,
|
vb,
|
||||||
descriptorIndex,
|
descriptorIndex,
|
||||||
|
@ -1234,12 +1247,15 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
vbSize,
|
vbSize,
|
||||||
vertexBuffer.Stride);
|
vertexBuffer.Stride);
|
||||||
|
|
||||||
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState);
|
buffer.BindVertexBuffer(Gd, Cbs, (uint)binding, ref _newState, _vertexBufferUpdater);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// May need to be rewritten. Bind this buffer before draw.
|
// May need to be rewritten. Bind this buffer before draw.
|
||||||
|
|
||||||
|
buffer.Dispose();
|
||||||
|
|
||||||
buffer = new VertexBufferState(
|
buffer = new VertexBufferState(
|
||||||
vertexBuffer.Buffer.Handle,
|
vertexBuffer.Buffer.Handle,
|
||||||
descriptorIndex,
|
descriptorIndex,
|
||||||
|
@ -1255,6 +1271,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_vertexBufferUpdater.Commit(Cbs);
|
||||||
|
|
||||||
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
_newState.VertexBindingDescriptionsCount = (uint)validCount;
|
||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
@ -1596,10 +1614,12 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
|
int i = BitOperations.TrailingZeroCount(_vertexBuffersDirty);
|
||||||
|
|
||||||
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState);
|
_vertexBuffers[i].BindVertexBuffer(Gd, Cbs, (uint)i, ref _newState, _vertexBufferUpdater);
|
||||||
|
|
||||||
_vertexBuffersDirty &= ~(1UL << i);
|
_vertexBuffersDirty &= ~(1UL << i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_vertexBufferUpdater.Commit(Cbs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_stateDirty || Pbp != pbp)
|
if (_stateDirty || Pbp != pbp)
|
||||||
|
@ -1712,6 +1732,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
_framebuffer?.Dispose();
|
_framebuffer?.Dispose();
|
||||||
_newState.Dispose();
|
_newState.Dispose();
|
||||||
_descriptorSetUpdater.Dispose();
|
_descriptorSetUpdater.Dispose();
|
||||||
|
_vertexBufferUpdater.Dispose();
|
||||||
|
|
||||||
for (int i = 0; i < _vertexBuffers.Length; i++)
|
for (int i = 0; i < _vertexBuffers.Length; i++)
|
||||||
{
|
{
|
||||||
|
|
|
@ -46,7 +46,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
AttributeScalarAlignment = 1;
|
AttributeScalarAlignment = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state)
|
public void BindVertexBuffer(VulkanRenderer gd, CommandBufferScoped cbs, uint binding, ref PipelineState state, VertexBufferUpdater updater)
|
||||||
{
|
{
|
||||||
var autoBuffer = _buffer;
|
var autoBuffer = _buffer;
|
||||||
|
|
||||||
|
@ -65,21 +65,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
|
|
||||||
var buffer = autoBuffer.Get(cbs, 0, newSize).Value;
|
var buffer = autoBuffer.Get(cbs, 0, newSize).Value;
|
||||||
|
|
||||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
updater.BindVertexBuffer(cbs, binding, buffer, 0, (ulong)newSize, (ulong)stride);
|
||||||
{
|
|
||||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
|
||||||
cbs.CommandBuffer,
|
|
||||||
binding,
|
|
||||||
1,
|
|
||||||
buffer,
|
|
||||||
0,
|
|
||||||
(ulong)newSize,
|
|
||||||
(ulong)stride);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
_buffer = autoBuffer;
|
_buffer = autoBuffer;
|
||||||
|
|
||||||
|
@ -106,21 +92,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
|
var buffer = autoBuffer.Get(cbs, _offset, _size).Value;
|
||||||
|
|
||||||
if (gd.Capabilities.SupportsExtendedDynamicState)
|
updater.BindVertexBuffer(cbs, binding, buffer, (ulong)_offset, (ulong)_size, (ulong)_stride);
|
||||||
{
|
|
||||||
gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
|
||||||
cbs.CommandBuffer,
|
|
||||||
binding,
|
|
||||||
1,
|
|
||||||
buffer,
|
|
||||||
(ulong)_offset,
|
|
||||||
(ulong)_size,
|
|
||||||
(ulong)_stride);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, binding, 1, buffer, (ulong)_offset);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +101,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||||
return _buffer == buffer;
|
return _buffer == buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Matches(Auto<DisposableBuffer> buffer, int descriptorIndex, int offset, int size, int stride = 0)
|
||||||
|
{
|
||||||
|
return _buffer == buffer && DescriptorIndex == descriptorIndex && _offset == offset && _size == size && _stride == stride;
|
||||||
|
}
|
||||||
|
|
||||||
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
{
|
{
|
||||||
if (_buffer == from)
|
if (_buffer == from)
|
||||||
|
|
84
src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs
Normal file
84
src/Ryujinx.Graphics.Vulkan/VertexBufferUpdater.cs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
using Silk.NET.Vulkan;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal class VertexBufferUpdater : IDisposable
|
||||||
|
{
|
||||||
|
private VulkanRenderer _gd;
|
||||||
|
|
||||||
|
private uint _baseBinding;
|
||||||
|
private uint _count;
|
||||||
|
|
||||||
|
private NativeArray<VkBuffer> _buffers;
|
||||||
|
private NativeArray<ulong> _offsets;
|
||||||
|
private NativeArray<ulong> _sizes;
|
||||||
|
private NativeArray<ulong> _strides;
|
||||||
|
|
||||||
|
public VertexBufferUpdater(VulkanRenderer gd)
|
||||||
|
{
|
||||||
|
_gd = gd;
|
||||||
|
|
||||||
|
_buffers = new NativeArray<VkBuffer>(Constants.MaxVertexBuffers);
|
||||||
|
_offsets = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||||
|
_sizes = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||||
|
_strides = new NativeArray<ulong>(Constants.MaxVertexBuffers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BindVertexBuffer(CommandBufferScoped cbs, uint binding, VkBuffer buffer, ulong offset, ulong size, ulong stride)
|
||||||
|
{
|
||||||
|
if (_count == 0)
|
||||||
|
{
|
||||||
|
_baseBinding = binding;
|
||||||
|
}
|
||||||
|
else if (_baseBinding + _count != binding)
|
||||||
|
{
|
||||||
|
Commit(cbs);
|
||||||
|
_baseBinding = binding;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = (int)_count;
|
||||||
|
|
||||||
|
_buffers[index] = buffer;
|
||||||
|
_offsets[index] = offset;
|
||||||
|
_sizes[index] = size;
|
||||||
|
_strides[index] = stride;
|
||||||
|
|
||||||
|
_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void Commit(CommandBufferScoped cbs)
|
||||||
|
{
|
||||||
|
if (_count != 0)
|
||||||
|
{
|
||||||
|
if (_gd.Capabilities.SupportsExtendedDynamicState)
|
||||||
|
{
|
||||||
|
_gd.ExtendedDynamicStateApi.CmdBindVertexBuffers2(
|
||||||
|
cbs.CommandBuffer,
|
||||||
|
_baseBinding,
|
||||||
|
_count,
|
||||||
|
_buffers.Pointer,
|
||||||
|
_offsets.Pointer,
|
||||||
|
_sizes.Pointer,
|
||||||
|
_strides.Pointer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_gd.Api.CmdBindVertexBuffers(cbs.CommandBuffer, _baseBinding, _count, _buffers.Pointer, _offsets.Pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_buffers.Dispose();
|
||||||
|
_offsets.Dispose();
|
||||||
|
_sizes.Dispose();
|
||||||
|
_strides.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue