From cfcebc4cd9e783f55d124fb9c6f9d9a88f0d1ac8 Mon Sep 17 00:00:00 2001 From: ParantezTech <12572227+par274@users.noreply.github.com> Date: Sat, 3 Oct 2026 02:33:41 +0300 Subject: [PATCH] [Vulkan] Write only the cache shards that gained pipelines A shard remembers the driver size it was loaded or saved with and is not written again while that size holds. Shard saves are logged as one summary line, and a shard load only when it is slow. --- ...ulkanVideoPresenter.PipelineCacheShards.cs | 50 +++++++++++++++---- .../VideoOut/VulkanPipelineCacheShardTests.cs | 40 +++++++++++++++ 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.PipelineCacheShards.cs b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.PipelineCacheShards.cs index 530039e..acd448a 100644 --- a/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.PipelineCacheShards.cs +++ b/src/SharpEmu.Libs/VideoOut/VulkanVideoPresenter.PipelineCacheShards.cs @@ -13,11 +13,14 @@ internal static unsafe partial class VulkanVideoPresenter { private sealed class DriverCacheShard { - public required Lazy Cache; + public Lazy Cache = null!; public required string Path; public bool Dirty; + public nuint SavedBytes; } + private const double SlowShardLoadMilliseconds = 50; + private string? _pipelineCacheShardDirectory; private readonly Dictionary _pipelineCacheShards = new(); private static readonly bool LegacyShaderGroups = @@ -54,10 +57,10 @@ internal static unsafe partial class VulkanVideoPresenter return existing.Cache; } - var path = Path.Combine(_pipelineCacheShardDirectory, key + ".bin"); - var source = new Lazy(() => LoadGuestPipelineCache(key, path)); - _pipelineCacheShards.Add(key, new DriverCacheShard { Cache = source, Path = path, Dirty = true }); - return source; + var shard = new DriverCacheShard { Path = Path.Combine(_pipelineCacheShardDirectory, key + ".bin"), Dirty = true }; + shard.Cache = new Lazy(() => LoadGuestPipelineCache(key, shard)); + _pipelineCacheShards.Add(key, shard); + return shard.Cache; } private PipelineCache ResolveGuestPipelineCache(Lazy? source) @@ -66,8 +69,9 @@ internal static unsafe partial class VulkanVideoPresenter return cache.Handle != 0 ? cache : _pipelineCache; } - private PipelineCache LoadGuestPipelineCache(string key, string path) + private PipelineCache LoadGuestPipelineCache(string key, DriverCacheShard shard) { + var path = shard.Path; byte[] data = []; try { @@ -90,19 +94,45 @@ internal static unsafe partial class VulkanVideoPresenter return default; } - if (data.Length != 0) - Console.Error.WriteLine($"[LOADER][INFO] Vulkan cache shard loaded: key={key} bytes={data.Length} ms={Stopwatch.GetElapsedTime(started).TotalMilliseconds:F1}"); + if (data.Length == 0) + return cache; + + var milliseconds = Stopwatch.GetElapsedTime(started).TotalMilliseconds; + if (QueryPipelineCacheSize(cache) is { } size) + shard.SavedBytes = size; + if (milliseconds >= SlowShardLoadMilliseconds) + Console.Error.WriteLine($"[LOADER][INFO] Vulkan cache shard loaded: key={key} bytes={data.Length} ms={milliseconds:F1}"); return cache; } private void SaveGuestPipelineCaches() { + var savedShards = 0; + nuint savedBytes = 0; foreach (var shard in _pipelineCacheShards.Values) { - if (shard.Dirty && shard.Cache.IsValueCreated && shard.Cache.Value.Handle != 0 && - SaveDriverPipelineCache(shard.Cache.Value, shard.Path, MaxPipelineCacheBytes, out _)) + if (!shard.Dirty || !shard.Cache.IsValueCreated || shard.Cache.Value.Handle == 0) + continue; + + var cache = shard.Cache.Value; + if (QueryPipelineCacheSize(cache) is { } size && size == shard.SavedBytes) + { shard.Dirty = false; + continue; + } + + if (SaveDriverPipelineCache(cache, shard.Path, MaxPipelineCacheBytes, out var bytes)) + { + shard.Dirty = false; + shard.SavedBytes = bytes; + savedShards++; + savedBytes += bytes; + } } + + if (savedShards != 0) + Console.Error.WriteLine( + $"[LOADER][INFO] Vulkan cache shards saved: directory={_pipelineCacheShardDirectory} shards={savedShards} bytes={savedBytes}"); } private void DestroyGuestPipelineCaches() diff --git a/tests/SharpEmu.Libs.Tests/VideoOut/VulkanPipelineCacheShardTests.cs b/tests/SharpEmu.Libs.Tests/VideoOut/VulkanPipelineCacheShardTests.cs index 8e6d058..477beb8 100644 --- a/tests/SharpEmu.Libs.Tests/VideoOut/VulkanPipelineCacheShardTests.cs +++ b/tests/SharpEmu.Libs.Tests/VideoOut/VulkanPipelineCacheShardTests.cs @@ -42,6 +42,46 @@ public sealed class VulkanPipelineCacheShardTests(HeadlessVulkanFixture fixture) } } + [Fact] + public void AnUnchangedShardIsNotWrittenAgain() + { + var vulkan = fixture.Vulkan; + if (!GatePrerequisites.Ready(vulkan)) return; + var directory = Path.Combine(Path.GetTempPath(), "SharpEmuTests", Guid.NewGuid().ToString("N")); + using var presenter = new PresenterUnderTest(vulkan); + presenter.SetField("_pipelineCacheShardDirectory", directory); + try + { + Directory.CreateDirectory(directory); + var path = Path.Combine(directory, "test.bin"); + _ = ((Lazy)presenter.InvokeMethod("GetGuestPipelineCacheSource", "test")!).Value; + presenter.InvokeMethod("SaveGuestPipelineCaches"); + Assert.True(File.Exists(path)); + + File.Delete(path); + presenter.InvokeMethod("GetGuestPipelineCacheSource", "test"); + presenter.InvokeMethod("SaveGuestPipelineCaches"); + Assert.False(File.Exists(path)); + + presenter.InvokeMethod("SaveGuestPipelineCaches"); + presenter.InvokeMethod("DestroyGuestPipelineCaches"); + _ = ((Lazy)presenter.InvokeMethod("GetGuestPipelineCacheSource", "test")!).Value; + presenter.InvokeMethod("SaveGuestPipelineCaches"); + Assert.True(File.Exists(path)); + + presenter.InvokeMethod("DestroyGuestPipelineCaches"); + _ = ((Lazy)presenter.InvokeMethod("GetGuestPipelineCacheSource", "test")!).Value; + File.Delete(path); + presenter.InvokeMethod("SaveGuestPipelineCaches"); + Assert.False(File.Exists(path)); + } + finally + { + presenter.InvokeMethod("DestroyGuestPipelineCaches"); + Directory.Delete(directory, recursive: true); + } + } + [Fact] public void ACheckpointWritesNewShardsAndWaitsForTheNextInterval() {