| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- using ICSharpCode.SharpZipLib.Zip;
- using Ryujinx.Common;
- using Ryujinx.Common.Configuration;
- using Ryujinx.Common.Logging;
- using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
- using Ryujinx.Graphics.Shader;
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Runtime.CompilerServices;
- using System.Runtime.InteropServices;
- namespace Ryujinx.Graphics.Gpu.Shader.Cache
- {
- /// <summary>
- /// Helper to manipulate the disk shader cache.
- /// </summary>
- static class CacheHelper
- {
- /// <summary>
- /// Compute a cache manifest from runtime data.
- /// </summary>
- /// <param name="version">The version of the cache</param>
- /// <param name="graphicsApi">The graphics api used by the cache</param>
- /// <param name="hashType">The hash type of the cache</param>
- /// <param name="entries">The entries in the cache</param>
- /// <returns>The cache manifest from runtime data</returns>
- public static byte[] ComputeManifest(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, HashSet<Hash128> entries)
- {
- if (hashType != CacheHashType.XxHash128)
- {
- throw new NotImplementedException($"{hashType}");
- }
- CacheManifestHeader manifestHeader = new CacheManifestHeader(version, graphicsApi, hashType);
- byte[] data = new byte[Unsafe.SizeOf<CacheManifestHeader>() + entries.Count * Unsafe.SizeOf<Hash128>()];
- // CacheManifestHeader has the same size as a Hash128.
- Span<Hash128> dataSpan = MemoryMarshal.Cast<byte, Hash128>(data.AsSpan()).Slice(1);
- int i = 0;
- foreach (Hash128 hash in entries)
- {
- dataSpan[i++] = hash;
- }
- manifestHeader.UpdateChecksum(data.AsSpan(Unsafe.SizeOf<CacheManifestHeader>()));
- MemoryMarshal.Write(data, ref manifestHeader);
- return data;
- }
- /// <summary>
- /// Get the base directory of the shader cache for a given title id.
- /// </summary>
- /// <param name="titleId">The title id of the target application</param>
- /// <returns>The base directory of the shader cache for a given title id</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetBaseCacheDirectory(string titleId) => Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
- /// <summary>
- /// Get the temp path to the cache data directory.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The temp path to the cache data directory</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetCacheTempDataPath(string cacheDirectory) => Path.Combine(cacheDirectory, "temp");
- /// <summary>
- /// The path to the cache archive file.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The path to the cache archive file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetArchivePath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.zip");
- /// <summary>
- /// The path to the cache manifest file.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <returns>The path to the cache manifest file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GetManifestPath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.info");
- /// <summary>
- /// Create a new temp path to the given cached file via its hash.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <param name="key">The hash of the cached data</param>
- /// <returns>New path to the given cached file</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static string GenCacheTempFilePath(string cacheDirectory, Hash128 key) => Path.Combine(GetCacheTempDataPath(cacheDirectory), key.ToString());
- /// <summary>
- /// Generate the path to the cache directory.
- /// </summary>
- /// <param name="baseCacheDirectory">The base of the cache directory</param>
- /// <param name="graphicsApi">The graphics api in use</param>
- /// <param name="shaderProvider">The name of the shader provider in use</param>
- /// <param name="cacheName">The name of the cache</param>
- /// <returns>The path to the cache directory</returns>
- public static string GenerateCachePath(string baseCacheDirectory, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName)
- {
- string graphicsApiName = graphicsApi switch
- {
- CacheGraphicsApi.OpenGL => "opengl",
- CacheGraphicsApi.OpenGLES => "opengles",
- CacheGraphicsApi.Vulkan => "vulkan",
- CacheGraphicsApi.DirectX => "directx",
- CacheGraphicsApi.Metal => "metal",
- CacheGraphicsApi.Guest => "guest",
- _ => throw new NotImplementedException(graphicsApi.ToString()),
- };
- return Path.Combine(baseCacheDirectory, graphicsApiName, shaderProvider, cacheName);
- }
- /// <summary>
- /// Read a cached file with the given hash that is present in the archive.
- /// </summary>
- /// <param name="archive">The archive in use</param>
- /// <param name="entry">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte[] ReadFromArchive(ZipFile archive, Hash128 entry)
- {
- if (archive != null)
- {
- ZipEntry archiveEntry = archive.GetEntry($"{entry}");
- if (archiveEntry != null)
- {
- try
- {
- byte[] result = new byte[archiveEntry.Size];
- using (Stream archiveStream = archive.GetInputStream(archiveEntry))
- {
- archiveStream.Read(result);
- return result;
- }
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {entry} from archive");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
- }
- }
- return null;
- }
- /// <summary>
- /// Read a cached file with the given hash that is not present in the archive.
- /// </summary>
- /// <param name="cacheDirectory">The cache directory</param>
- /// <param name="entry">The given hash</param>
- /// <returns>The cached file if present or null</returns>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static byte[] ReadFromFile(string cacheDirectory, Hash128 entry)
- {
- string cacheTempFilePath = GenCacheTempFilePath(cacheDirectory, entry);
- try
- {
- return File.ReadAllBytes(cacheTempFilePath);
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
- return null;
- }
- /// <summary>
- /// Read transform feedback descriptors from guest.
- /// </summary>
- /// <param name="data">The raw guest transform feedback descriptors</param>
- /// <param name="header">The guest shader program header</param>
- /// <returns>The transform feedback descriptors read from guest</returns>
- public static TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header)
- {
- if (header.TransformFeedbackCount != 0)
- {
- TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount];
- for (int i = 0; i < result.Length; i++)
- {
- GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data);
- result[i] = new TransformFeedbackDescriptorOld(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>(), feedbackHeader.VaryingLocationsLength).ToArray());
- data = data.Slice(Unsafe.SizeOf<GuestShaderCacheTransformFeedbackHeader>() + feedbackHeader.VaryingLocationsLength);
- }
- return result;
- }
- return null;
- }
- /// <summary>
- /// Save temporary files not in archive.
- /// </summary>
- /// <param name="baseCacheDirectory">The base of the cache directory</param>
- /// <param name="archive">The archive to use</param>
- /// <param name="entries">The entries in the cache</param>
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipFile archive, HashSet<Hash128> entries)
- {
- List<string> filesToDelete = new List<string>();
- archive.BeginUpdate();
- foreach (Hash128 hash in entries)
- {
- string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
- if (File.Exists(cacheTempFilePath))
- {
- string cacheHash = $"{hash}";
- ZipEntry entry = archive.GetEntry(cacheHash);
- if (entry != null)
- {
- archive.Delete(entry);
- }
- // We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression.
- archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated);
- filesToDelete.Add(cacheTempFilePath);
- }
- }
- archive.CommitUpdate();
- foreach (string filePath in filesToDelete)
- {
- File.Delete(filePath);
- }
- }
- public static bool IsArchiveReadOnly(string archivePath)
- {
- FileInfo info = new FileInfo(archivePath);
- if (!info.Exists)
- {
- return false;
- }
- try
- {
- using (FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None))
- {
- return false;
- }
- }
- catch (IOException)
- {
- return true;
- }
- }
- }
- }
|