[GPU] Skip native binds that repeat the bound state

This commit is contained in:
ParantezTech 2026-10-11 01:39:04 +03:00
parent ae97ac9525
commit 1fbecd3233
3 changed files with 268 additions and 8 deletions

View File

@ -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);
}
}
}
}

View File

@ -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)
{

View File

@ -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.