[Profile] Attribute render thread GPU waits to their call sites

This commit is contained in:
ParantezTech 2026-10-11 06:11:54 +03:00
parent 22ec74e085
commit e5c2755789
4 changed files with 103 additions and 0 deletions

View File

@ -172,10 +172,13 @@ internal sealed unsafe class VulkanAsyncReadback : IDisposable
PSemaphores = &semaphore,
PValues = &signalValue,
};
var waitStart = VideoOut.GpuWaitSiteProfile.Begin();
using (VideoOut.RenderPhaseProfile.MeasureDetail(VideoOut.RenderPhaseProfile.Phase.GpuCompletionWait))
{
Require(vk.WaitSemaphores(_device.Device, &waitInfo, ulong.MaxValue), "vkWaitSemaphores(readback)");
}
VideoOut.GpuWaitSiteProfile.End(waitStart);
}
finally
{

View File

@ -220,10 +220,13 @@ internal sealed unsafe class VulkanTickDevice : IGpuTickDevice
PValues = &tick,
};
Result result;
var waitStart = GpuWaitSiteProfile.Begin();
using (RenderPhaseProfile.MeasureDetail(RenderPhaseProfile.Phase.GpuCompletionWait))
{
result = _vk.WaitSemaphores(_device, &waitInfo, ulong.MaxValue);
}
GpuWaitSiteProfile.End(waitStart);
failure = result.ToString();
return result == Result.Success && WaitRetired(tick);
}

View File

@ -0,0 +1,96 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Diagnostics;
namespace SharpEmu.Libs.VideoOut;
internal static class GpuWaitSiteProfile
{
private const int SiteFrames = 5;
private static readonly object _gate = new();
private static readonly Dictionary<string, (long Count, long Ticks)> _sites = new(StringComparer.Ordinal);
private static readonly HashSet<string> PlumbingTypes = new(StringComparer.Ordinal)
{
"GpuWaitSiteProfile",
"TickTimeline",
"SubmissionScheduler",
"VulkanTickDevice",
"VulkanAsyncReadback",
"GpuWorkerRelay",
"GpuWaitSpin",
};
public static long Begin() => RenderPhaseProfile.DetailMeasurementsEnabled ? Stopwatch.GetTimestamp() : 0;
public static void End(long start)
{
if (start == 0)
{
return;
}
var elapsed = Stopwatch.GetTimestamp() - start;
var site = Site();
lock (_gate)
{
_sites.TryGetValue(site, out var entry);
_sites[site] = (entry.Count + 1, entry.Ticks + elapsed);
}
}
private static string Site()
{
var names = new List<string>(SiteFrames);
foreach (var frame in new StackTrace(2, false).GetFrames())
{
if (frame.GetMethod() is not { } method)
{
continue;
}
var type = method.DeclaringType;
while (type is { DeclaringType: not null } && type.Name.StartsWith('<'))
{
type = type.DeclaringType;
}
var typeName = type?.Name ?? "?";
if (PlumbingTypes.Contains(typeName) || method.Name.StartsWith('<'))
{
continue;
}
names.Add($"{typeName}.{method.Name}");
if (names.Count == SiteFrames)
{
break;
}
}
return string.Join(" < ", names);
}
public static void Report()
{
List<KeyValuePair<string, (long Count, long Ticks)>> ranked;
lock (_gate)
{
if (_sites.Count == 0)
{
return;
}
ranked = _sites.OrderByDescending(pair => pair.Value.Ticks).Take(12).ToList();
_sites.Clear();
}
foreach (var (site, (count, ticks)) in ranked)
{
Console.Error.WriteLine(
$"[PERF][GPU_WAIT_SITE] ms={ticks * 1000.0 / Stopwatch.Frequency:F1} n={count} site={site}");
}
}
}

View File

@ -465,6 +465,7 @@ internal static class RenderPhaseProfile
$"[PERF][RENDER_MS] window_s={seconds:F1} frames={frames} " +
string.Join(" ", parts.Select(part => $"{part.Phase}={part.Milliseconds:F2}ms/n{part.Entries}")));
GpuWaitSiteProfile.Report();
ReportImageUploads();
BufferUploadProfile.Report();
Console.Error.WriteLine(SharpEmu.ShaderCompiler.Resources.ResourceMaterializationCache.TakeReport());