[GPU] Skip native binds that repeat the bound state
This commit is contained in:
parent
ae97ac9525
commit
1fbecd3233
|
|
@ -0,0 +1,249 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Libs.Gpu.Rendering;
|
||||
using SharpEmu.Libs.Gpu.Vulkan;
|
||||
using SharpEmu.ShaderCompiler.Resources;
|
||||
using Silk.NET.Vulkan;
|
||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||
|
||||
namespace SharpEmu.Libs.VideoOut;
|
||||
|
||||
internal static unsafe partial class VulkanVideoPresenter
|
||||
{
|
||||
private static readonly bool NativeStateReuse =
|
||||
Environment.GetEnvironmentVariable("SHARPEMU_NATIVE_STATE_REUSE") != "0";
|
||||
|
||||
private sealed partial class Presenter
|
||||
{
|
||||
private const int ShadowVertexBufferCapacity = 32;
|
||||
|
||||
private nint _shadowCommand;
|
||||
private ulong _shadowTick;
|
||||
private long _shadowEpoch = -1;
|
||||
private Pipeline _shadowGraphicsPipeline;
|
||||
private Pipeline _shadowComputePipeline;
|
||||
private PipelineLayout _shadowPushLayout;
|
||||
private ShaderStageFlags _shadowPushStages;
|
||||
private uint[]? _shadowPushData;
|
||||
private DescriptorShadow? _graphicsDescriptorShadow;
|
||||
private DescriptorShadow? _computeDescriptorShadow;
|
||||
private ulong[]? _shadowVertexBindings;
|
||||
private int _shadowVertexCount = -1;
|
||||
private VkBuffer _shadowIndexBuffer;
|
||||
private ulong _shadowIndexOffset;
|
||||
private IndexType _shadowIndexType;
|
||||
private bool _shadowIndexValid;
|
||||
private ulong[]? _descriptorKeyScratch;
|
||||
|
||||
private sealed class DescriptorShadow
|
||||
{
|
||||
public bool Valid;
|
||||
public PipelineLayout Layout;
|
||||
public ulong[] Key = new ulong[256];
|
||||
public int Length;
|
||||
}
|
||||
|
||||
private bool SyncNativeShadow(CommandBuffer command)
|
||||
{
|
||||
if (!NativeStateReuse)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var tick = _scheduler.CurrentTick;
|
||||
var epoch = NativeBindingEpoch.Value;
|
||||
if (command.Handle == _shadowCommand && tick == _shadowTick && epoch == _shadowEpoch)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_shadowCommand = command.Handle;
|
||||
_shadowTick = tick;
|
||||
_shadowEpoch = epoch;
|
||||
_shadowGraphicsPipeline = default;
|
||||
_shadowComputePipeline = default;
|
||||
_shadowPushLayout = default;
|
||||
if (_graphicsDescriptorShadow is not null)
|
||||
{
|
||||
_graphicsDescriptorShadow.Valid = false;
|
||||
}
|
||||
|
||||
if (_computeDescriptorShadow is not null)
|
||||
{
|
||||
_computeDescriptorShadow.Valid = false;
|
||||
}
|
||||
|
||||
_shadowVertexCount = -1;
|
||||
_shadowIndexValid = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void BindPipelineShadowed(CommandBuffer command, PipelineBindPoint bindPoint, Pipeline pipeline)
|
||||
{
|
||||
if (SyncNativeShadow(command))
|
||||
{
|
||||
ref var bound = ref bindPoint == PipelineBindPoint.Compute ? ref _shadowComputePipeline : ref _shadowGraphicsPipeline;
|
||||
var reused = bound.Handle == pipeline.Handle;
|
||||
CountNativeState(NativeStateKind.Pipeline, reused);
|
||||
if (reused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bound = pipeline;
|
||||
}
|
||||
|
||||
_vk.CmdBindPipeline(command, bindPoint, pipeline);
|
||||
}
|
||||
|
||||
private void PushConstantsShadowed(CommandBuffer command, PipelineLayout layout, ShaderStageFlags stages, uint* data)
|
||||
{
|
||||
if (SyncNativeShadow(command))
|
||||
{
|
||||
var values = new ReadOnlySpan<uint>(data, (int)PushData.DwordCount);
|
||||
var shadowData = _shadowPushData ??= new uint[PushData.DwordCount];
|
||||
var reused = _shadowPushLayout.Handle == layout.Handle && _shadowPushStages == stages && values.SequenceEqual(shadowData);
|
||||
CountNativeState(NativeStateKind.PushConstants, reused);
|
||||
if (reused)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_shadowPushLayout = layout;
|
||||
_shadowPushStages = stages;
|
||||
values.CopyTo(shadowData);
|
||||
}
|
||||
|
||||
_vk.CmdPushConstants(command, layout, stages, 0, PushData.ByteSize, data);
|
||||
}
|
||||
|
||||
private bool DescriptorsAlreadyBound(CommandBuffer command, PipelineBindPoint bindPoint, PipelineLayout layout, ReadOnlySpan<WriteDescriptorSet> writes)
|
||||
{
|
||||
if (!SyncNativeShadow(command))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = DescriptorKey(writes);
|
||||
var shadow = bindPoint == PipelineBindPoint.Compute
|
||||
? _computeDescriptorShadow ??= new DescriptorShadow()
|
||||
: _graphicsDescriptorShadow ??= new DescriptorShadow();
|
||||
var key = _descriptorKeyScratch.AsSpan(0, length);
|
||||
var reused = shadow.Valid && shadow.Layout.Handle == layout.Handle && key.SequenceEqual(shadow.Key.AsSpan(0, shadow.Length));
|
||||
CountNativeState(NativeStateKind.Descriptors, reused);
|
||||
if (reused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shadow.Key.Length < length)
|
||||
{
|
||||
shadow.Key = new ulong[Math.Max(length, shadow.Key.Length * 2)];
|
||||
}
|
||||
|
||||
key.CopyTo(shadow.Key);
|
||||
shadow.Length = length;
|
||||
shadow.Layout = layout;
|
||||
shadow.Valid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int DescriptorKey(ReadOnlySpan<WriteDescriptorSet> writes)
|
||||
{
|
||||
var length = 0;
|
||||
_descriptorKeyScratch ??= new ulong[256];
|
||||
foreach (ref readonly var write in writes)
|
||||
{
|
||||
var needed = length + 2 + (int)write.DescriptorCount * 3;
|
||||
if (_descriptorKeyScratch.Length < needed)
|
||||
{
|
||||
Array.Resize(ref _descriptorKeyScratch, Math.Max(needed, _descriptorKeyScratch.Length * 2));
|
||||
}
|
||||
|
||||
var key = _descriptorKeyScratch;
|
||||
key[length++] = write.DstBinding | ((ulong)write.DescriptorType << 32);
|
||||
key[length++] = write.DescriptorCount | (write.PBufferInfo != null ? 1UL << 32 : 0) | (write.PImageInfo != null ? 1UL << 33 : 0);
|
||||
for (var index = 0; index < write.DescriptorCount; index++)
|
||||
{
|
||||
if (write.PBufferInfo != null)
|
||||
{
|
||||
var info = write.PBufferInfo[index];
|
||||
key[length++] = info.Buffer.Handle;
|
||||
key[length++] = info.Offset;
|
||||
key[length++] = info.Range;
|
||||
}
|
||||
else if (write.PImageInfo != null)
|
||||
{
|
||||
var info = write.PImageInfo[index];
|
||||
key[length++] = info.Sampler.Handle;
|
||||
key[length++] = info.ImageView.Handle;
|
||||
key[length++] = (ulong)info.ImageLayout;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
private bool VertexBuffersAlreadyBound(CommandBuffer command, ReadOnlySpan<BufferBinding> bindings)
|
||||
{
|
||||
if (!SyncNativeShadow(command) || bindings.Length > ShadowVertexBufferCapacity)
|
||||
{
|
||||
_shadowVertexCount = -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
var shadow = _shadowVertexBindings ??= new ulong[ShadowVertexBufferCapacity * 2];
|
||||
var reused = _shadowVertexCount == bindings.Length;
|
||||
for (var index = 0; reused && index < bindings.Length; index++)
|
||||
{
|
||||
reused = shadow[index * 2] == bindings[index].Handle && shadow[index * 2 + 1] == bindings[index].Offset;
|
||||
}
|
||||
|
||||
CountNativeState(NativeStateKind.VertexBuffers, reused);
|
||||
if (reused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var index = 0; index < bindings.Length; index++)
|
||||
{
|
||||
shadow[index * 2] = bindings[index].Handle;
|
||||
shadow[index * 2 + 1] = bindings[index].Offset;
|
||||
}
|
||||
|
||||
_shadowVertexCount = bindings.Length;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IndexBufferAlreadyBound(CommandBuffer command, VkBuffer buffer, ulong offset, IndexType type)
|
||||
{
|
||||
if (!SyncNativeShadow(command))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var reused = _shadowIndexValid && _shadowIndexBuffer.Handle == buffer.Handle && _shadowIndexOffset == offset && _shadowIndexType == type;
|
||||
CountNativeState(NativeStateKind.IndexBuffer, reused);
|
||||
if (reused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
_shadowIndexBuffer = buffer;
|
||||
_shadowIndexOffset = offset;
|
||||
_shadowIndexType = type;
|
||||
_shadowIndexValid = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CountNativeState(NativeStateKind kind, bool reused)
|
||||
{
|
||||
if (GpuSyncProfile.Enabled)
|
||||
{
|
||||
GpuSyncProfile.CountNativeState(kind, reused);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1092,10 +1092,10 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
if (hasPushData)
|
||||
{
|
||||
var pushStages = bindPoint == PipelineBindPoint.Graphics ? ShaderStageFlags.VertexBit | ShaderStageFlags.FragmentBit : ShaderStageFlags.ComputeBit;
|
||||
_vk.CmdPushConstants(command, entry.Layout, pushStages, 0, PushData.ByteSize, pushData);
|
||||
PushConstantsShadowed(command, entry.Layout, pushStages, pushData);
|
||||
}
|
||||
|
||||
if (writeIndex != 0)
|
||||
if (writeIndex != 0 && !DescriptorsAlreadyBound(command, bindPoint, entry.Layout, writes[..writeIndex]))
|
||||
{
|
||||
fixed (WriteDescriptorSet* writePointer = writes)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -381,6 +381,11 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
{
|
||||
if (bindings.IsEmpty) return;
|
||||
var command = BeginBatchedGuestCommands();
|
||||
if (VertexBuffersAlreadyBound(command, bindings))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var buffers = stackalloc VkBuffer[bindings.Length];
|
||||
var offsets = stackalloc ulong[bindings.Length];
|
||||
for (var index = 0; index < bindings.Length; index++)
|
||||
|
|
@ -392,8 +397,14 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
_vk.CmdBindVertexBuffers(command, 0, (uint)bindings.Length, buffers, offsets);
|
||||
}
|
||||
|
||||
public void BindIndexBuffer(BufferBinding binding, IndexType type) =>
|
||||
_vk.CmdBindIndexBuffer(BeginBatchedGuestCommands(), new VkBuffer(binding.Handle), binding.Offset, type);
|
||||
public void BindIndexBuffer(BufferBinding binding, IndexType type)
|
||||
{
|
||||
var command = BeginBatchedGuestCommands();
|
||||
if (!IndexBufferAlreadyBound(command, new VkBuffer(binding.Handle), binding.Offset, type))
|
||||
{
|
||||
_vk.CmdBindIndexBuffer(command, new VkBuffer(binding.Handle), binding.Offset, type);
|
||||
}
|
||||
}
|
||||
|
||||
public IResourcePreparation BeginPreparation()
|
||||
{
|
||||
|
|
@ -997,13 +1008,13 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
_boundGraphicsPipeline = entry;
|
||||
if (!entry.RectangleList)
|
||||
{
|
||||
_vk.CmdBindPipeline(command, bindPoint, entry.Pipeline);
|
||||
BindPipelineShadowed(command, bindPoint, entry.Pipeline);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_vk.CmdBindPipeline(command, bindPoint, entry.Pipeline);
|
||||
BindPipelineShadowed(command, bindPoint, entry.Pipeline);
|
||||
_profileComputePipeline = entry.Id;
|
||||
}
|
||||
|
||||
|
|
@ -1027,7 +1038,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
variant = CreateRenderPipeline(entry.Description!, strip ? PrimitiveTopology.TriangleStrip : PrimitiveTopology.TriangleList, entry.Layout);
|
||||
}
|
||||
|
||||
_vk.CmdBindPipeline(command, PipelineBindPoint.Graphics, variant);
|
||||
BindPipelineShadowed(command, PipelineBindPoint.Graphics, variant);
|
||||
}
|
||||
|
||||
private void BindNativeRectangleList(RenderPipelineEntry entry, CommandBuffer command)
|
||||
|
|
@ -1038,7 +1049,7 @@ internal static unsafe partial class VulkanVideoPresenter
|
|||
entry.Layout, PolygonMode.FillRectangleNV);
|
||||
}
|
||||
|
||||
_vk.CmdBindPipeline(command, PipelineBindPoint.Graphics, entry.RectangleVariant);
|
||||
BindPipelineShadowed(command, PipelineBindPoint.Graphics, entry.RectangleVariant);
|
||||
}
|
||||
|
||||
// Rectangle2D consumes three vertices and fills their projected bounding box.
|
||||
|
|
|
|||
Loading…
Reference in New Issue