From 0de84e34dffaf94cd49edf83df677022c1210457 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Sun, 11 Oct 2026 01:35:45 +0300 Subject: [PATCH] [Diagnostics] Count GPU barriers and rendering scopes --- .../Gpu/Vulkan/GpuSyncProfile.cs | 79 +++++++++++++++++++ .../Gpu/Vulkan/VulkanSynchronization.cs | 5 ++ .../VideoOut/RenderPhaseProfile.cs | 1 + .../VulkanVideoPresenter.Rendering.cs | 10 +++ 4 files changed, 95 insertions(+) create mode 100644 src/SharpEmu.Libs/Gpu/Vulkan/GpuSyncProfile.cs diff --git a/src/SharpEmu.Libs/Gpu/Vulkan/GpuSyncProfile.cs b/src/SharpEmu.Libs/Gpu/Vulkan/GpuSyncProfile.cs new file mode 100644 index 0000000..d5fbd62 --- /dev/null +++ b/src/SharpEmu.Libs/Gpu/Vulkan/GpuSyncProfile.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2026 SharpEmu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +using Silk.NET.Vulkan; + +namespace SharpEmu.Libs.Gpu.Vulkan; + +internal enum NativeStateKind +{ + Pipeline, + PushConstants, + Descriptors, + VertexBuffers, + IndexBuffer, + Count, +} + +internal static class GpuSyncProfile +{ + private static readonly long[] _nativeStateRecorded = new long[(int)NativeStateKind.Count]; + private static readonly long[] _nativeStateReused = new long[(int)NativeStateKind.Count]; + private static long _barriers; + private static long _drainingBarriers; + private static long _memoryBarriers; + private static long _bufferBarriers; + private static long _imageBarriers; + private static long _mergedRequests; + private static long _renderingScopes; + private static long _batchFlushes; + + public static bool Enabled => VideoOut.RenderPhaseProfile.Enabled; + + public static void CountBarrier(PipelineStageFlags source, PipelineStageFlags destination, uint memory, uint buffer, uint image) + { + Interlocked.Increment(ref _barriers); + if ((source & PipelineStageFlags.AllCommandsBit) != 0 && (destination & (PipelineStageFlags.AllCommandsBit | PipelineStageFlags.TopOfPipeBit | PipelineStageFlags.DrawIndirectBit)) != 0) + { + Interlocked.Increment(ref _drainingBarriers); + } + + Interlocked.Add(ref _memoryBarriers, memory); + Interlocked.Add(ref _bufferBarriers, buffer); + Interlocked.Add(ref _imageBarriers, image); + } + + public static void CountMergedRequest() => Interlocked.Increment(ref _mergedRequests); + + public static void CountRenderingScope() => Interlocked.Increment(ref _renderingScopes); + + public static void CountBatchFlush() => Interlocked.Increment(ref _batchFlushes); + + public static void CountNativeState(NativeStateKind kind, bool reused) + { + if (reused) + { + Interlocked.Increment(ref _nativeStateReused[(int)kind]); + } + else + { + Interlocked.Increment(ref _nativeStateRecorded[(int)kind]); + } + } + + private static string TakeNativeStateReport() + { + var parts = new string[(int)NativeStateKind.Count]; + for (var index = 0; index < parts.Length; index++) + { + var reused = Interlocked.Exchange(ref _nativeStateReused[index], 0); + var recorded = Interlocked.Exchange(ref _nativeStateRecorded[index], 0); + parts[index] = FormattableString.Invariant($"{(NativeStateKind)index}={reused}/{reused + recorded}"); + } + + return string.Join(" ", parts); + } + + public static string TakeReport() => FormattableString.Invariant( + $"[PERF][GPU_SYNC] barriers={Interlocked.Exchange(ref _barriers, 0)} draining={Interlocked.Exchange(ref _drainingBarriers, 0)} memory={Interlocked.Exchange(ref _memoryBarriers, 0)} buffer={Interlocked.Exchange(ref _bufferBarriers, 0)} image={Interlocked.Exchange(ref _imageBarriers, 0)} merged_requests={Interlocked.Exchange(ref _mergedRequests, 0)} rendering_scopes={Interlocked.Exchange(ref _renderingScopes, 0)} draw_count_flushes={Interlocked.Exchange(ref _batchFlushes, 0)} reused {TakeNativeStateReport()}"); +} diff --git a/src/SharpEmu.Libs/Gpu/Vulkan/VulkanSynchronization.cs b/src/SharpEmu.Libs/Gpu/Vulkan/VulkanSynchronization.cs index d275742..93cecea 100644 --- a/src/SharpEmu.Libs/Gpu/Vulkan/VulkanSynchronization.cs +++ b/src/SharpEmu.Libs/Gpu/Vulkan/VulkanSynchronization.cs @@ -22,6 +22,11 @@ internal static unsafe class VulkanSynchronization uint imageMemoryBarrierCount, ImageMemoryBarrier2* imageMemoryBarriers) { + if (GpuSyncProfile.Enabled) + { + GpuSyncProfile.CountBarrier(sourceStages, destinationStages, memoryBarrierCount, bufferMemoryBarrierCount, imageMemoryBarrierCount); + } + var sourceStages2 = (PipelineStageFlags2)(ulong)sourceStages; var destinationStages2 = (PipelineStageFlags2)(ulong)destinationStages; for (var index = 0u; index < memoryBarrierCount; index++) diff --git a/src/SharpEmu.Libs/VideoOut/RenderPhaseProfile.cs b/src/SharpEmu.Libs/VideoOut/RenderPhaseProfile.cs index 8821b5d..33b1c50 100644 --- a/src/SharpEmu.Libs/VideoOut/RenderPhaseProfile.cs +++ b/src/SharpEmu.Libs/VideoOut/RenderPhaseProfile.cs @@ -469,6 +469,7 @@ internal static class RenderPhaseProfile BufferUploadProfile.Report(); Console.Error.WriteLine(SharpEmu.ShaderCompiler.Resources.ResourceMaterializationCache.TakeReport()); Console.Error.WriteLine(SharpEmu.Libs.Gpu.Buffers.GuestBufferCache.TakeAsyncReadbackReport()); + Console.Error.WriteLine(SharpEmu.Libs.Gpu.Vulkan.GpuSyncProfile.TakeReport()); Console.Error.WriteLine(SharpEmu.Libs.Gpu.Images.GuestImageCache.TakeLookupReport()); SharpEmu.ShaderCompiler.Resources.ResourceMaterializationProfile.WriteReport(); SharpEmu.Libs.Diagnostics.AgcRegisterPacketProfile.WriteReport(); diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs index 3c0fe2c..c1faff6 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.Rendering.cs @@ -407,6 +407,11 @@ internal static unsafe partial class VulkanVideoPresenter if (_batchDrawCount >= (_renderingActive ? DrawsPerBatchInRenderPass : DrawsPerBatch)) { + if (GpuSyncProfile.Enabled) + { + GpuSyncProfile.CountBatchFlush(); + } + EndRendering(); FlushBatchedGuestCommands(); } @@ -927,6 +932,11 @@ internal static unsafe partial class VulkanVideoPresenter }; _vk.CmdBeginRendering(command, &rendering); _renderingScopesBegun++; + if (GpuSyncProfile.Enabled) + { + GpuSyncProfile.CountRenderingScope(); + } + _renderingActive = true; _renderingState = state; }