CacheHelper.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. using ICSharpCode.SharpZipLib.Zip;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Configuration;
  4. using Ryujinx.Common.Logging;
  5. using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
  6. using Ryujinx.Graphics.Shader;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Runtime.CompilerServices;
  11. using System.Runtime.InteropServices;
  12. namespace Ryujinx.Graphics.Gpu.Shader.Cache
  13. {
  14. /// <summary>
  15. /// Helper to manipulate the disk shader cache.
  16. /// </summary>
  17. static class CacheHelper
  18. {
  19. /// <summary>
  20. /// Compute a cache manifest from runtime data.
  21. /// </summary>
  22. /// <param name="version">The version of the cache</param>
  23. /// <param name="graphicsApi">The graphics api used by the cache</param>
  24. /// <param name="hashType">The hash type of the cache</param>
  25. /// <param name="entries">The entries in the cache</param>
  26. /// <returns>The cache manifest from runtime data</returns>
  27. public static byte[] ComputeManifest(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, HashSet<Hash128> entries)
  28. {
  29. if (hashType != CacheHashType.XxHash128)
  30. {
  31. throw new NotImplementedException($"{hashType}");
  32. }
  33. CacheManifestHeader manifestHeader = new CacheManifestHeader(version, graphicsApi, hashType);
  34. byte[] data = new byte[Unsafe.SizeOf<CacheManifestHeader>() + entries.Count * Unsafe.SizeOf<Hash128>()];
  35. // CacheManifestHeader has the same size as a Hash128.
  36. Span<Hash128> dataSpan = MemoryMarshal.Cast<byte, Hash128>(data.AsSpan()).Slice(1);
  37. int i = 0;
  38. foreach (Hash128 hash in entries)
  39. {
  40. dataSpan[i++] = hash;
  41. }
  42. manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
  43. MemoryMarshal.Write(data, ref manifestHeader);
  44. return data;
  45. }
  46. /// <summary>
  47. /// Get the base directory of the shader cache for a given title id.
  48. /// </summary>
  49. /// <param name="titleId">The title id of the target application</param>
  50. /// <returns>The base directory of the shader cache for a given title id</returns>
  51. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  52. public static string GetBaseCacheDirectory(string titleId) => Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
  53. /// <summary>
  54. /// Get the temp path to the cache data directory.
  55. /// </summary>
  56. /// <param name="cacheDirectory">The cache directory</param>
  57. /// <returns>The temp path to the cache data directory</returns>
  58. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  59. public static string GetCacheTempDataPath(string cacheDirectory) => Path.Combine(cacheDirectory, "temp");
  60. /// <summary>
  61. /// The path to the cache archive file.
  62. /// </summary>
  63. /// <param name="cacheDirectory">The cache directory</param>
  64. /// <returns>The path to the cache archive file</returns>
  65. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  66. public static string GetArchivePath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.zip");
  67. /// <summary>
  68. /// The path to the cache manifest file.
  69. /// </summary>
  70. /// <param name="cacheDirectory">The cache directory</param>
  71. /// <returns>The path to the cache manifest file</returns>
  72. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  73. public static string GetManifestPath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.info");
  74. /// <summary>
  75. /// Create a new temp path to the given cached file via its hash.
  76. /// </summary>
  77. /// <param name="cacheDirectory">The cache directory</param>
  78. /// <param name="key">The hash of the cached data</param>
  79. /// <returns>New path to the given cached file</returns>
  80. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  81. public static string GenCacheTempFilePath(string cacheDirectory, Hash128 key) => Path.Combine(GetCacheTempDataPath(cacheDirectory), key.ToString());
  82. /// <summary>
  83. /// Generate the path to the cache directory.
  84. /// </summary>
  85. /// <param name="baseCacheDirectory">The base of the cache directory</param>
  86. /// <param name="graphicsApi">The graphics api in use</param>
  87. /// <param name="shaderProvider">The name of the shader provider in use</param>
  88. /// <param name="cacheName">The name of the cache</param>
  89. /// <returns>The path to the cache directory</returns>
  90. public static string GenerateCachePath(string baseCacheDirectory, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName)
  91. {
  92. string graphicsApiName = graphicsApi switch
  93. {
  94. CacheGraphicsApi.OpenGL => "opengl",
  95. CacheGraphicsApi.OpenGLES => "opengles",
  96. CacheGraphicsApi.Vulkan => "vulkan",
  97. CacheGraphicsApi.DirectX => "directx",
  98. CacheGraphicsApi.Metal => "metal",
  99. CacheGraphicsApi.Guest => "guest",
  100. _ => throw new NotImplementedException(graphicsApi.ToString()),
  101. };
  102. return Path.Combine(baseCacheDirectory, graphicsApiName, shaderProvider, cacheName);
  103. }
  104. /// <summary>
  105. /// Read a cached file with the given hash that is present in the archive.
  106. /// </summary>
  107. /// <param name="archive">The archive in use</param>
  108. /// <param name="entry">The given hash</param>
  109. /// <returns>The cached file if present or null</returns>
  110. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  111. public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
  112. {
  113. if (archive != null)
  114. {
  115. ZipEntry archiveEntry = archive.GetEntry($"{entry}");
  116. if (archiveEntry != null)
  117. {
  118. try
  119. {
  120. byte[] result = new byte[archiveEntry.Size];
  121. using (Stream archiveStream = archive.GetInputStream(archiveEntry))
  122. {
  123. archiveStream.Read(result);
  124. return result;
  125. }
  126. }
  127. catch (Exception e)
  128. {
  129. Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {entry} from archive");
  130. Logger.Error?.Print(LogClass.Gpu, e.ToString());
  131. }
  132. }
  133. }
  134. return null;
  135. }
  136. /// <summary>
  137. /// Read a cached file with the given hash that is not present in the archive.
  138. /// </summary>
  139. /// <param name="cacheDirectory">The cache directory</param>
  140. /// <param name="entry">The given hash</param>
  141. /// <returns>The cached file if present or null</returns>
  142. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  143. public static byte[] ReadFromFile(string cacheDirectory, Hash128 entry)
  144. {
  145. string cacheTempFilePath = GenCacheTempFilePath(cacheDirectory, entry);
  146. try
  147. {
  148. return File.ReadAllBytes(cacheTempFilePath);
  149. }
  150. catch (Exception e)
  151. {
  152. Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
  153. Logger.Error?.Print(LogClass.Gpu, e.ToString());
  154. }
  155. return null;
  156. }
  157. /// <summary>
  158. /// Read transform feedback descriptors from guest.
  159. /// </summary>
  160. /// <param name="data">The raw guest transform feedback descriptors</param>
  161. /// <param name="header">The guest shader program header</param>
  162. /// <returns>The transform feedback descriptors read from guest</returns>
  163. public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
  164. {
  165. if (header.TransformFeedbackCount != 0)
  166. {
  167. TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
  168. for (int i = 0; i < result.Length; i++)
  169. {
  170. GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
  171. result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
  172. data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
  173. }
  174. return result;
  175. }
  176. return null;
  177. }
  178. /// <summary>
  179. /// Save temporary files not in archive.
  180. /// </summary>
  181. /// <param name="baseCacheDirectory">The base of the cache directory</param>
  182. /// <param name="archive">The archive to use</param>
  183. /// <param name="entries">The entries in the cache</param>
  184. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  185. public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
  186. {
  187. List<string> filesToDelete = new List<string>();
  188. archive.BeginUpdate();
  189. foreach (Hash128 hash in entries)
  190. {
  191. string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
  192. if (File.Exists(cacheTempFilePath))
  193. {
  194. string cacheHash = $"{hash}";
  195. ZipEntry entry = archive.GetEntry(cacheHash);
  196. if (entry != null)
  197. {
  198. archive.Delete(entry);
  199. }
  200. // We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
  201. archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
  202. filesToDelete.Add(cacheTempFilePath);
  203. }
  204. }
  205. archive.CommitUpdate();
  206. foreach (string filePath in filesToDelete)
  207. {
  208. File.Delete(filePath);
  209. }
  210. }
  211. public static bool IsArchiveReadOnly(string archivePath)
  212. {
  213. FileInfo info = new FileInfo(archivePath);
  214. if (!info.Exists)
  215. {
  216. return false;
  217. }
  218. try
  219. {
  220. using (FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None))
  221. {
  222. return false;
  223. }
  224. }
  225. catch (IOException)
  226. {
  227. return true;
  228. }
  229. }
  230. }
  231. }