CacheMigration.cs 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. using ICSharpCode.SharpZipLib.Zip;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Logging;
  4. using Ryujinx.Graphics.GAL;
  5. using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.IO;
  9. namespace Ryujinx.Graphics.Gpu.Shader.Cache
  10. {
  11. /// <summary>
  12. /// Class handling shader cache migrations.
  13. /// </summary>
  14. static class CacheMigration
  15. {
  16. /// <summary>
  17. /// Check if the given cache version need to recompute its hash.
  18. /// </summary>
  19. /// <param name="version">The version in use</param>
  20. /// <param name="newVersion">The new version after migration</param>
  21. /// <returns>True if a hash recompute is needed</returns>
  22. public static bool NeedHashRecompute(ulong version, out ulong newVersion)
  23. {
  24. const ulong TargetBrokenVersion = 1717;
  25. const ulong TargetFixedVersion = 1759;
  26. newVersion = TargetFixedVersion;
  27. if (version == TargetBrokenVersion)
  28. {
  29. return true;
  30. }
  31. return false;
  32. }
  33. private class StreamZipEntryDataSource : IStaticDataSource
  34. {
  35. private readonly ZipFile Archive;
  36. private readonly ZipEntry Entry;
  37. public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
  38. {
  39. Archive = archive;
  40. Entry = entry;
  41. }
  42. public Stream GetSource()
  43. {
  44. return Archive.GetInputStream(Entry);
  45. }
  46. }
  47. /// <summary>
  48. /// Move a file with the name of a given hash to another in the cache archive.
  49. /// </summary>
  50. /// <param name="archive">The archive in use</param>
  51. /// <param name="oldKey">The old key</param>
  52. /// <param name="newKey">The new key</param>
  53. private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
  54. {
  55. ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
  56. if (oldGuestEntry != null)
  57. {
  58. archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
  59. archive.Delete(oldGuestEntry);
  60. }
  61. }
  62. /// <summary>
  63. /// Recompute all the hashes of a given cache.
  64. /// </summary>
  65. /// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
  66. /// <param name="hostBaseCacheDirectory">The host cache directory path</param>
  67. /// <param name="graphicsApi">The graphics api in use</param>
  68. /// <param name="hashType">The hash type in use</param>
  69. /// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
  70. private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion)
  71. {
  72. string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory);
  73. string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory);
  74. if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries))
  75. {
  76. CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries);
  77. Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration...");
  78. string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
  79. string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
  80. ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
  81. ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
  82. CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
  83. CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
  84. int programIndex = 0;
  85. HashSet<Hash128> newEntries = new HashSet<Hash128>();
  86. foreach (Hash128 oldHash in guestEntries)
  87. {
  88. byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash);
  89. Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})");
  90. if (guestProgram != null)
  91. {
  92. ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
  93. ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
  94. TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
  95. Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd);
  96. if (newHash != oldHash)
  97. {
  98. MoveEntry(guestArchive, oldHash, newHash);
  99. MoveEntry(hostArchive, oldHash, newHash);
  100. }
  101. else
  102. {
  103. Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}");
  104. }
  105. newEntries.Add(newHash);
  106. }
  107. programIndex++;
  108. }
  109. byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries);
  110. byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries);
  111. File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
  112. File.WriteAllBytes(hostManifestPath, newHostManifestContent);
  113. guestArchive.CommitUpdate();
  114. hostArchive.CommitUpdate();
  115. guestArchive.Close();
  116. hostArchive.Close();
  117. }
  118. }
  119. /// <summary>
  120. /// Check and run cache migration if needed.
  121. /// </summary>
  122. /// <param name="baseCacheDirectory">The base path of the cache</param>
  123. /// <param name="graphicsApi">The graphics api in use</param>
  124. /// <param name="hashType">The hash type in use</param>
  125. /// <param name="shaderProvider">The shader provider name of the cache</param>
  126. public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider)
  127. {
  128. string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
  129. string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host");
  130. string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
  131. string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
  132. bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
  133. if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
  134. {
  135. if (NeedHashRecompute(header.Version, out ulong newVersion))
  136. {
  137. RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
  138. }
  139. }
  140. }
  141. }
  142. }