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 { /// /// Class handling shader cache migrations. /// static class CacheMigration { /// /// Check if the given cache version need to recompute its hash. /// /// The version in use /// The new version after migration /// True if a hash recompute is needed 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; } /// /// Move a file with the name of a given hash to another in the cache archive. /// /// The archive in use /// The old key /// The new key 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(); } } /// /// Recompute all the hashes of a given cache. /// /// The guest cache directory path /// The host cache directory path /// The graphics api in use /// The hash type in use /// The version to write in the host and guest manifest after migration 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 guestEntries)) { CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet 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 newEntries = new HashSet(); 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 guestProgramReadOnlySpan = guestProgram; ReadOnlySpan 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(); } } /// /// Check and run cache migration if needed. /// /// The base path of the cache /// The graphics api in use /// The hash type in use /// The shader provider name of the cache 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); } } } } }