diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs')
-rw-r--r-- | Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs | 158 |
1 files changed, 158 insertions, 0 deletions
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs new file mode 100644 index 00000000..965287b5 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs @@ -0,0 +1,158 @@ +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +namespace Ryujinx.Graphics.Gpu.Shader.Cache +{ + /// <summary> + /// Class handling shader cache migrations. + /// </summary> + static class CacheMigration + { + /// <summary> + /// Check if the given cache version need to recompute its hash. + /// </summary> + /// <param name="version">The version in use</param> + /// <param name="newVersion">The new version after migration</param> + /// <returns>True if a hash recompute is needed</returns> + public static bool NeedHashRecompute(ulong version, out ulong newVersion) + { + const ulong TargetBrokenVersion = 1717; + const ulong TargetFixedVersion = 1759; + + newVersion = TargetFixedVersion; + + if (version == TargetBrokenVersion) + { + return true; + } + + return false; + } + + /// <summary> + /// Move a file with the name of a given hash to another in the cache archive. + /// </summary> + /// <param name="archive">The archive in use</param> + /// <param name="oldKey">The old key</param> + /// <param name="newKey">The new key</param> + private static void MoveEntry(ZipArchive archive, Hash128 oldKey, Hash128 newKey) + { + ZipArchiveEntry oldGuestEntry = archive.GetEntry($"{oldKey}"); + + if (oldGuestEntry != null) + { + ZipArchiveEntry newGuestEntry = archive.CreateEntry($"{newKey}"); + + using (Stream oldStream = oldGuestEntry.Open()) + using (Stream newStream = newGuestEntry.Open()) + { + oldStream.CopyTo(newStream); + } + + oldGuestEntry.Delete(); + } + } + + /// <summary> + /// Recompute all the hashes of a given cache. + /// </summary> + /// <param name="guestBaseCacheDirectory">The guest cache directory path</param> + /// <param name="hostBaseCacheDirectory">The host cache directory path</param> + /// <param name="graphicsApi">The graphics api in use</param> + /// <param name="hashType">The hash type in use</param> + /// <param name="newVersion">The version to write in the host and guest manifest after migration</param> + private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion) + { + string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory); + string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory); + + if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries)) + { + CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries); + + Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration..."); + + string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory); + string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory); + + ZipArchive guestArchive = ZipFile.Open(guestArchivePath, ZipArchiveMode.Update); + ZipArchive hostArchive = ZipFile.Open(hostArchivePath, ZipArchiveMode.Update); + + CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries); + CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries); + + int programIndex = 0; + + HashSet<Hash128> newEntries = new HashSet<Hash128>(); + + foreach (Hash128 oldHash in guestEntries) + { + byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash); + + Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})"); + + if (guestProgram != null) + { + ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram; + + ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); + + TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader); + + Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd); + + if (newHash != oldHash) + { + MoveEntry(guestArchive, oldHash, newHash); + MoveEntry(hostArchive, oldHash, newHash); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}"); + } + + newEntries.Add(newHash); + } + + programIndex++; + } + + byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries); + byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries); + + File.WriteAllBytes(guestManifestPath, newGuestManifestContent); + File.WriteAllBytes(hostManifestPath, newHostManifestContent); + + guestArchive.Dispose(); + hostArchive.Dispose(); + } + } + + /// <summary> + /// Check and run cache migration if needed. + /// </summary> + /// <param name="baseCacheDirectory">The base path of the cache</param> + /// <param name="graphicsApi">The graphics api in use</param> + /// <param name="hashType">The hash type in use</param> + /// <param name="shaderProvider">The shader provider name of the cache</param> + public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider) + { + string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program"); + string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host"); + + if (CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header)) + { + if (NeedHashRecompute(header.Version, out ulong newVersion)) + { + RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion); + } + } + } + } +} |