CacheMigration.cs 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. using Ryujinx.Common;
  2. using Ryujinx.Common.Logging;
  3. using Ryujinx.Graphics.GAL;
  4. using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.IO;
  8. using System.IO.Compression;
  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. /// <summary>
  34. /// Move a file with the name of a given hash to another in the cache archive.
  35. /// </summary>
  36. /// <param name="archive">The archive in use</param>
  37. /// <param name="oldKey">The old key</param>
  38. /// <param name="newKey">The new key</param>
  39. private static void MoveEntry(ZipArchive archive, Hash128 oldKey, Hash128 newKey)
  40. {
  41. ZipArchiveEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
  42. if (oldGuestEntry != null)
  43. {
  44. ZipArchiveEntry newGuestEntry = archive.CreateEntry($"{newKey}");
  45. using (Stream oldStream = oldGuestEntry.Open())
  46. using (Stream newStream = newGuestEntry.Open())
  47. {
  48. oldStream.CopyTo(newStream);
  49. }
  50. oldGuestEntry.Delete();
  51. }
  52. }
  53. /// <summary>
  54. /// Recompute all the hashes of a given cache.
  55. /// </summary>
  56. /// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
  57. /// <param name="hostBaseCacheDirectory">The host cache directory path</param>
  58. /// <param name="graphicsApi">The graphics api in use</param>
  59. /// <param name="hashType">The hash type in use</param>
  60. /// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
  61. private static void RecomputeHashes(string guestBaseCacheDirectory, string hostBaseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, ulong newVersion)
  62. {
  63. string guestManifestPath = CacheHelper.GetManifestPath(guestBaseCacheDirectory);
  64. string hostManifestPath = CacheHelper.GetManifestPath(hostBaseCacheDirectory);
  65. if (CacheHelper.TryReadManifestFile(guestManifestPath, CacheGraphicsApi.Guest, hashType, out _, out HashSet<Hash128> guestEntries))
  66. {
  67. CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> hostEntries);
  68. Logger.Info?.Print(LogClass.Gpu, "Shader cache hashes need to be recomputed, performing migration...");
  69. string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
  70. string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
  71. ZipArchive guestArchive = ZipFile.Open(guestArchivePath, ZipArchiveMode.Update);
  72. ZipArchive hostArchive = ZipFile.Open(hostArchivePath, ZipArchiveMode.Update);
  73. CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
  74. CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
  75. int programIndex = 0;
  76. HashSet<Hash128> newEntries = new HashSet<Hash128>();
  77. foreach (Hash128 oldHash in guestEntries)
  78. {
  79. byte[] guestProgram = CacheHelper.ReadFromArchive(guestArchive, oldHash);
  80. Logger.Info?.Print(LogClass.Gpu, $"Migrating shader {oldHash} ({programIndex + 1} / {guestEntries.Count})");
  81. if (guestProgram != null)
  82. {
  83. ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram;
  84. ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
  85. TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader);
  86. Hash128 newHash = CacheHelper.ComputeGuestHashFromCache(cachedShaderEntries, tfd);
  87. if (newHash != oldHash)
  88. {
  89. MoveEntry(guestArchive, oldHash, newHash);
  90. MoveEntry(hostArchive, oldHash, newHash);
  91. }
  92. else
  93. {
  94. Logger.Warning?.Print(LogClass.Gpu, $"Same hashes for shader {oldHash}");
  95. }
  96. newEntries.Add(newHash);
  97. }
  98. programIndex++;
  99. }
  100. byte[] newGuestManifestContent = CacheHelper.ComputeManifest(newVersion, CacheGraphicsApi.Guest, hashType, newEntries);
  101. byte[] newHostManifestContent = CacheHelper.ComputeManifest(newVersion, graphicsApi, hashType, newEntries);
  102. File.WriteAllBytes(guestManifestPath, newGuestManifestContent);
  103. File.WriteAllBytes(hostManifestPath, newHostManifestContent);
  104. guestArchive.Dispose();
  105. hostArchive.Dispose();
  106. }
  107. }
  108. /// <summary>
  109. /// Check and run cache migration if needed.
  110. /// </summary>
  111. /// <param name="baseCacheDirectory">The base path of the cache</param>
  112. /// <param name="graphicsApi">The graphics api in use</param>
  113. /// <param name="hashType">The hash type in use</param>
  114. /// <param name="shaderProvider">The shader provider name of the cache</param>
  115. public static void Run(string baseCacheDirectory, CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider)
  116. {
  117. string guestBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program");
  118. string hostBaseCacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, "host");
  119. string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
  120. string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
  121. bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
  122. if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
  123. {
  124. if (NeedHashRecompute(header.Version, out ulong newVersion))
  125. {
  126. RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
  127. }
  128. }
  129. }
  130. }
  131. }