diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
index 0f4db4f35e..9b61ef4a6e 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
@@ -125,26 +125,26 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
/// Get the temp path to the cache data directory.
///
/// The temp path to the cache data directory
- private string GetCacheTempDataPath() => Path.Combine(_cacheDirectory, "temp");
+ private string GetCacheTempDataPath() => CacheHelper.GetCacheTempDataPath(_cacheDirectory);
///
/// The path to the cache archive file.
///
/// The path to the cache archive file
- private string GetArchivePath() => Path.Combine(_cacheDirectory, "cache.zip");
+ private string GetArchivePath() => CacheHelper.GetArchivePath(_cacheDirectory);
///
/// The path to the cache manifest file.
///
/// The path to the cache manifest file
- private string GetManifestPath() => Path.Combine(_cacheDirectory, "cache.info");
+ private string GetManifestPath() => CacheHelper.GetManifestPath(_cacheDirectory);
///
/// Create a new temp path to the given cached file via its hash.
///
/// The hash of the cached data
/// New path to the given cached file
- private string GenCacheTempFilePath(Hash128 key) => Path.Combine(GetCacheTempDataPath(), key.ToString());
+ private string GenCacheTempFilePath(Hash128 key) => CacheHelper.GenCacheTempFilePath(_cacheDirectory, key);
///
/// Create a new cache collection.
@@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
throw new NotImplementedException($"{hashType}");
}
- _cacheDirectory = GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
+ _cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
_graphicsApi = graphicsApi;
_hashType = hashType;
_version = version;
@@ -178,13 +178,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
///
private void Load()
{
- bool isInvalid = false;
+ bool isValid = false;
- if (!Directory.Exists(_cacheDirectory))
- {
- isInvalid = true;
- }
- else
+ if (Directory.Exists(_cacheDirectory))
{
string manifestPath = GetManifestPath();
@@ -196,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
{
Memory hashTableRaw = rawManifest.Slice(Unsafe.SizeOf());
- isInvalid = !manifestHeader.IsValid(_version, _graphicsApi, _hashType, hashTableRaw.Span);
+ isValid = manifestHeader.IsValid(_graphicsApi, _hashType, hashTableRaw.Span) && _version == manifestHeader.Version;
- if (!isInvalid)
+ if (isValid)
{
ReadOnlySpan hashTable = MemoryMarshal.Cast(hashTableRaw.Span);
@@ -209,13 +205,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
}
}
}
- else
- {
- isInvalid = true;
- }
}
- if (isInvalid)
+ if (!isValid)
{
Logger.Warning?.Print(LogClass.Gpu, $"Shader collection \"{_cacheDirectory}\" got invalidated, cache will need to be rebuilt.");
@@ -324,22 +316,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
// Update the content of the zip.
lock (_hashTable)
{
- foreach (Hash128 hash in _hashTable)
- {
- string cacheTempFilePath = GenCacheTempFilePath(hash);
-
- if (File.Exists(cacheTempFilePath))
- {
- string cacheHash = $"{hash}";
-
- ZipArchiveEntry entry = _cacheArchive.GetEntry(cacheHash);
-
- entry?.Delete();
-
- _cacheArchive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
- File.Delete(cacheTempFilePath);
- }
- }
+ CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable);
// Close the instance to force a flush.
_cacheArchive.Dispose();
@@ -362,56 +339,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
///
private void SaveManifest()
{
- CacheManifestHeader manifestHeader = new CacheManifestHeader(_version, _graphicsApi, _hashType);
-
byte[] data;
lock (_hashTable)
{
- data = new byte[Unsafe.SizeOf() + _hashTable.Count * Unsafe.SizeOf()];
-
- // CacheManifestHeader has the same size as a Hash128.
- Span dataSpan = MemoryMarshal.Cast(data.AsSpan()).Slice(1);
-
- int i = 0;
-
- foreach (Hash128 hash in _hashTable)
- {
- dataSpan[i++] = hash;
- }
+ data = CacheHelper.ComputeManifest(_version, _graphicsApi, _hashType, _hashTable);
}
- manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf()));
-
- MemoryMarshal.Write(data, ref manifestHeader);
-
File.WriteAllBytes(GetManifestPath(), data);
}
- ///
- /// Generate the path to the cache directory.
- ///
- /// The base of the cache directory
- /// The graphics api in use
- /// The name of the shader provider in use
- /// The name of the cache
- /// The path to the cache directory
- private 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);
- }
-
///
/// Get a cached file with the given hash.
///
@@ -438,27 +375,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
if (found)
{
- ZipArchiveEntry archiveEntry = _cacheArchive.GetEntry($"{keyHash}");
-
- if (archiveEntry != null)
- {
- try
- {
- byte[] result = new byte[archiveEntry.Length];
-
- using (Stream archiveStream = archiveEntry.Open())
- {
- archiveStream.Read(result);
-
- return result;
- }
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {keyHash} from archive");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
- }
+ return CacheHelper.ReadFromArchive(_cacheArchive, keyHash);
}
return null;
@@ -480,17 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
if (found)
{
- string cacheTempFilePath = GenCacheTempFilePath(keyHash);
-
- try
- {
- return File.ReadAllBytes(GenCacheTempFilePath(keyHash));
- }
- catch (Exception e)
- {
- Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
- Logger.Error?.Print(LogClass.Gpu, e.ToString());
- }
+ return CacheHelper.ReadFromFile(GetCacheTempDataPath(), keyHash);
}
return null;
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
new file mode 100644
index 0000000000..1d492214eb
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
@@ -0,0 +1,482 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache
+{
+ ///
+ /// Helper to manipulate the disk shader cache.
+ ///
+ static class CacheHelper
+ {
+ ///
+ /// Try to read the manifest header from a given file path.
+ ///
+ /// The path to the manifest file
+ /// The manifest header read
+ /// Return true if the manifest header was read
+ public static bool TryReadManifestHeader(string manifestPath, out CacheManifestHeader header)
+ {
+ header = default;
+
+ if (File.Exists(manifestPath))
+ {
+ Memory rawManifest = File.ReadAllBytes(manifestPath);
+
+ if (MemoryMarshal.TryRead(rawManifest.Span, out header))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Try to read the manifest from a given file path.
+ ///
+ /// The path to the manifest file
+ /// The graphics api used by the cache
+ /// The hash type of the cache
+ /// The manifest header read
+ /// The entries read from the cache manifest
+ /// Return true if the manifest was read
+ public static bool TryReadManifestFile(string manifestPath, CacheGraphicsApi graphicsApi, CacheHashType hashType, out CacheManifestHeader header, out HashSet entries)
+ {
+ header = default;
+ entries = new HashSet();
+
+ if (File.Exists(manifestPath))
+ {
+ Memory rawManifest = File.ReadAllBytes(manifestPath);
+
+ if (MemoryMarshal.TryRead(rawManifest.Span, out header))
+ {
+ Memory hashTableRaw = rawManifest.Slice(Unsafe.SizeOf());
+
+ bool isValid = header.IsValid(graphicsApi, hashType, hashTableRaw.Span);
+
+ if (isValid)
+ {
+ ReadOnlySpan hashTable = MemoryMarshal.Cast(hashTableRaw.Span);
+
+ foreach (Hash128 hash in hashTable)
+ {
+ entries.Add(hash);
+ }
+ }
+
+ return isValid;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Compute a cache manifest from runtime data.
+ ///
+ /// The version of the cache
+ /// The graphics api used by the cache
+ /// The hash type of the cache
+ /// The entries in the cache
+ /// The cache manifest from runtime data
+ public static byte[] ComputeManifest(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, HashSet entries)
+ {
+ if (hashType != CacheHashType.XxHash128)
+ {
+ throw new NotImplementedException($"{hashType}");
+ }
+
+ CacheManifestHeader manifestHeader = new CacheManifestHeader(version, graphicsApi, hashType);
+
+ byte[] data = new byte[Unsafe.SizeOf() + entries.Count * Unsafe.SizeOf()];
+
+ // CacheManifestHeader has the same size as a Hash128.
+ Span dataSpan = MemoryMarshal.Cast(data.AsSpan()).Slice(1);
+
+ int i = 0;
+
+ foreach (Hash128 hash in entries)
+ {
+ dataSpan[i++] = hash;
+ }
+
+ manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf()));
+
+ MemoryMarshal.Write(data, ref manifestHeader);
+
+ return data;
+ }
+
+ ///
+ /// Get the base directory of the shader cache for a given title id.
+ ///
+ /// The title id of the target application
+ /// The base directory of the shader cache for a given title id
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string GetBaseCacheDirectory(string titleId) => Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
+
+ ///
+ /// Get the temp path to the cache data directory.
+ ///
+ /// The cache directory
+ /// The temp path to the cache data directory
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string GetCacheTempDataPath(string cacheDirectory) => Path.Combine(cacheDirectory, "temp");
+
+ ///
+ /// The path to the cache archive file.
+ ///
+ /// The cache directory
+ /// The path to the cache archive file
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string GetArchivePath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.zip");
+
+ ///
+ /// The path to the cache manifest file.
+ ///
+ /// The cache directory
+ /// The path to the cache manifest file
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string GetManifestPath(string cacheDirectory) => Path.Combine(cacheDirectory, "cache.info");
+
+ ///
+ /// Create a new temp path to the given cached file via its hash.
+ ///
+ /// The cache directory
+ /// The hash of the cached data
+ /// New path to the given cached file
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static string GenCacheTempFilePath(string cacheDirectory, Hash128 key) => Path.Combine(GetCacheTempDataPath(cacheDirectory), key.ToString());
+
+ ///
+ /// Generate the path to the cache directory.
+ ///
+ /// The base of the cache directory
+ /// The graphics api in use
+ /// The name of the shader provider in use
+ /// The name of the cache
+ /// The path to the cache directory
+ 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);
+ }
+
+ ///
+ /// Read a cached file with the given hash that is present in the archive.
+ ///
+ /// The archive in use
+ /// The given hash
+ /// The cached file if present or null
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static byte[] ReadFromArchive(ZipArchive archive, Hash128 entry)
+ {
+ if (archive != null)
+ {
+ ZipArchiveEntry archiveEntry = archive.GetEntry($"{entry}");
+
+ if (archiveEntry != null)
+ {
+ try
+ {
+ byte[] result = new byte[archiveEntry.Length];
+
+ using (Stream archiveStream = archiveEntry.Open())
+ {
+ 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;
+ }
+
+ ///
+ /// Read a cached file with the given hash that is not present in the archive.
+ ///
+ /// The cache directory
+ /// The given hash
+ /// The cached file if present or null
+ [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;
+ }
+
+ ///
+ /// Compute the guest program code for usage while dumping to disk or hash.
+ ///
+ /// The guest shader entries to use
+ /// The transform feedback descriptors
+ /// Used to determine if the guest program code is generated for hashing
+ /// The guest program code for usage while dumping to disk or hash
+ private static byte[] ComputeGuestProgramCode(ReadOnlySpan cachedShaderEntries, TransformFeedbackDescriptor[] tfd, bool forHashCompute = false)
+ {
+ using (MemoryStream stream = new MemoryStream())
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ foreach (GuestShaderCacheEntry cachedShaderEntry in cachedShaderEntries)
+ {
+ if (cachedShaderEntry != null)
+ {
+ // Code (and Code A if present)
+ stream.Write(cachedShaderEntry.Code);
+
+ if (forHashCompute)
+ {
+ // Guest GPU accessor header (only write this for hashes, already present in the header for dumps)
+ writer.WriteStruct(cachedShaderEntry.Header.GpuAccessorHeader);
+ }
+
+ // Texture descriptors
+ foreach (GuestTextureDescriptor textureDescriptor in cachedShaderEntry.TextureDescriptors.Values)
+ {
+ writer.WriteStruct(textureDescriptor);
+ }
+ }
+ }
+
+ // Transformation feedback
+ if (tfd != null)
+ {
+ foreach (TransformFeedbackDescriptor transform in tfd)
+ {
+ writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
+ writer.Write(transform.VaryingLocations);
+ }
+ }
+
+ return stream.ToArray();
+ }
+ }
+
+ ///
+ /// Compute a guest hash from shader entries.
+ ///
+ /// The guest shader entries to use
+ /// The optional transform feedback descriptors
+ /// A guest hash from shader entries
+ public static Hash128 ComputeGuestHashFromCache(ReadOnlySpan cachedShaderEntries, TransformFeedbackDescriptor[] tfd = null)
+ {
+ return XXHash128.ComputeHash(ComputeGuestProgramCode(cachedShaderEntries, tfd, true));
+ }
+
+ ///
+ /// Read transform feedback descriptors from guest.
+ ///
+ /// The raw guest transform feedback descriptors
+ /// The guest shader program header
+ /// The transform feedback descriptors read from guest
+ public static TransformFeedbackDescriptor[] ReadTransformationFeedbackInformations(ref ReadOnlySpan data, GuestShaderCacheHeader header)
+ {
+ if (header.TransformFeedbackCount != 0)
+ {
+ TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read(data);
+
+ result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf(), feedbackHeader.VaryingLocationsLength).ToArray());
+
+ data = data.Slice(Unsafe.SizeOf() + feedbackHeader.VaryingLocationsLength);
+ }
+
+ return result;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Create a new instance of from an gpu accessor.
+ ///
+ /// The gpu accessor
+ /// A new instance of
+ public static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
+ {
+ return new GuestGpuAccessorHeader
+ {
+ ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
+ ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
+ ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
+ ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
+ ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
+ PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
+ };
+ }
+
+ ///
+ /// Create guest shader cache entries from the runtime contexts.
+ ///
+ /// The GPU memory manager in use
+ /// The runtime contexts
+ /// Guest shader cahe entries from the runtime contexts
+ public static GuestShaderCacheEntry[] CreateShaderCacheEntries(MemoryManager memoryManager, ReadOnlySpan shaderContexts)
+ {
+ GuestShaderCacheEntry[] entries = new GuestShaderCacheEntry[shaderContexts.Length];
+
+ for (int i = 0; i < shaderContexts.Length; i++)
+ {
+ TranslatorContext context = shaderContexts[i];
+
+ if (context == null)
+ {
+ continue;
+ }
+
+ int sizeA = context.AddressA == 0 ? 0 : context.SizeA;
+
+ byte[] code = new byte[context.Size + sizeA];
+
+ memoryManager.GetSpan(context.Address, context.Size).CopyTo(code);
+
+ if (context.AddressA != 0)
+ {
+ memoryManager.GetSpan(context.AddressA, context.SizeA).CopyTo(code.AsSpan().Slice(context.Size, context.SizeA));
+ }
+
+ GuestGpuAccessorHeader gpuAccessorHeader = CreateGuestGpuAccessorCache(context.GpuAccessor);
+
+ if (context.GpuAccessor is GpuAccessor)
+ {
+ gpuAccessorHeader.TextureDescriptorCount = context.TextureHandlesForCache.Count;
+ }
+
+ GuestShaderCacheEntryHeader header = new GuestShaderCacheEntryHeader(context.Stage, context.Size, sizeA, gpuAccessorHeader);
+
+ GuestShaderCacheEntry entry = new GuestShaderCacheEntry(header, code);
+
+ if (context.GpuAccessor is GpuAccessor gpuAccessor)
+ {
+ foreach (int textureHandle in context.TextureHandlesForCache)
+ {
+ GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle)).ToCache();
+
+ textureDescriptor.Handle = (uint)textureHandle;
+
+ entry.TextureDescriptors.Add(textureHandle, textureDescriptor);
+ }
+ }
+
+ entries[i] = entry;
+ }
+
+ return entries;
+ }
+
+ ///
+ /// Create a guest shader program.
+ ///
+ /// The entries composing the guest program dump
+ /// The transform feedback descriptors in use
+ /// The resulting guest shader program
+ public static byte[] CreateGuestProgramDump(GuestShaderCacheEntry[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd = null)
+ {
+ using (MemoryStream resultStream = new MemoryStream())
+ {
+ BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
+
+ byte transformFeedbackCount = 0;
+
+ if (tfd != null)
+ {
+ transformFeedbackCount = (byte)tfd.Length;
+ }
+
+ // Header
+ resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
+
+ // Write all entries header
+ foreach (GuestShaderCacheEntry entry in shaderCacheEntries)
+ {
+ if (entry == null)
+ {
+ resultStreamWriter.WriteStruct(new GuestShaderCacheEntryHeader());
+ }
+ else
+ {
+ resultStreamWriter.WriteStruct(entry.Header);
+ }
+ }
+
+ // Finally, write all program code and all transform feedback information.
+ resultStreamWriter.Write(ComputeGuestProgramCode(shaderCacheEntries, tfd));
+
+ return resultStream.ToArray();
+ }
+ }
+
+ ///
+ /// Save temporary files not in archive.
+ ///
+ /// The base of the cache directory
+ /// The archive to use
+ /// The entries in the cache
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void EnsureArchiveUpToDate(string baseCacheDirectory, ZipArchive archive, HashSet entries)
+ {
+ foreach (Hash128 hash in entries)
+ {
+ string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash);
+
+ if (File.Exists(cacheTempFilePath))
+ {
+ string cacheHash = $"{hash}";
+
+ ZipArchiveEntry entry = archive.GetEntry(cacheHash);
+
+ entry?.Delete();
+
+ archive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
+
+ File.Delete(cacheTempFilePath);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
index f977e96bc9..ca0070fdcf 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
@@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
///
/// Version of the guest cache shader (to increment when guest cache structure change).
///
- private const ulong GuestCacheVersion = 1717;
+ private const ulong GuestCacheVersion = 1759;
///
/// Create a new cache manager instance
@@ -45,7 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
_hashType = hashType;
_shaderProvider = shaderProvider;
- string baseCacheDirectory = Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
+ string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId);
+
+ CacheMigration.Run(baseCacheDirectory, graphicsApi, hashType, shaderProvider);
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
@@ -80,16 +82,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
_hostProgramCache.Synchronize();
}
- ///
- /// Computes the hash of some data using the current cache hashing algorithm.
- ///
- /// Some data to generate a hash for.
- /// The hash of some data using the current hashing algorithm of the cache
- public Hash128 ComputeHash(ReadOnlySpan data)
- {
- return XXHash128.ComputeHash(data);
- }
-
///
/// Save a shader program not present in the program cache.
///
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
new file mode 100644
index 0000000000..965287b54f
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
@@ -0,0 +1,158 @@
+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);
+ }
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
index 3f198dca5d..0601451dde 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
@@ -84,14 +84,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
///
/// Check the validity of the header.
///
- /// The target version in use
/// The target graphics api in use
/// The target hash type in use
/// The data after this header
/// True if the header is valid
- public bool IsValid(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan data)
+ /// This doesn't check that versions match
+ public bool IsValid(CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan data)
{
- return Version == version && GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
+ return GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
index 45a442e2bd..373fa6c643 100644
--- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
@@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
///
/// The header of the cached shader entry
/// The code of this shader
- private GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
+ public GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
{
Header = header;
Code = code;
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index b469aab52b..a04affc206 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -9,9 +9,6 @@ using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.IO;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -37,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Version of the codegen (to be changed when codegen or guest format change).
///
- private const ulong ShaderCodeGenVersion = 1717;
+ private const ulong ShaderCodeGenVersion = 1759;
///
/// Creates a new instance of the shader cache.
@@ -165,7 +162,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
List shaderPrograms = new List();
- TransformFeedbackDescriptor[] tfd = ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader);
+ TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader);
TranslationFlags flags = DefaultFlags;
@@ -347,14 +344,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
bool isShaderCacheEnabled = _cacheManager != null;
- byte[] programCode = null;
Hash128 programCodeHash = default;
- GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
+ GuestShaderCacheEntry[] shaderCacheEntries = null;
if (isShaderCacheEnabled)
{
// Compute hash and prepare data for shader disk cache comparison.
- GetProgramInformations(null, shaderContexts, out programCode, out programCodeHash, out shaderCacheEntries);
+ shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
+ programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries);
}
ShaderBundle cpShader;
@@ -381,7 +378,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (isShaderCacheEnabled)
{
_cpProgramsDiskCache.Add(programCodeHash, cpShader);
- _cacheManager.SaveProgram(ref programCodeHash, CreateGuestProgramDump(programCode, shaderCacheEntries, null), hostProgramBinary);
+ _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary);
}
}
@@ -451,14 +448,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
bool isShaderCacheEnabled = _cacheManager != null;
- byte[] programCode = null;
Hash128 programCodeHash = default;
- GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
+ GuestShaderCacheEntry[] shaderCacheEntries = null;
if (isShaderCacheEnabled)
{
// Compute hash and prepare data for shader disk cache comparison.
- GetProgramInformations(tfd, shaderContexts, out programCode, out programCodeHash, out shaderCacheEntries);
+ shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
+ programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd);
}
ShaderBundle gpShaders;
@@ -507,7 +504,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (isShaderCacheEnabled)
{
_gpProgramsDiskCache.Add(programCodeHash, gpShaders);
- _cacheManager.SaveProgram(ref programCodeHash, CreateGuestProgramDump(programCode, shaderCacheEntries, tfd), hostProgramBinary);
+ _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary);
}
}
@@ -766,191 +763,5 @@ namespace Ryujinx.Graphics.Gpu.Shader
_cacheManager?.Dispose();
}
-
- ///
- /// Create a guest shader program.
- ///
- /// The program code of the shader code
- /// The resulting guest shader entries header
- /// The transform feedback descriptors in use
- /// The resulting guest shader program
- private static byte[] CreateGuestProgramDump(ReadOnlySpan programCode, GuestShaderCacheEntryHeader[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd)
- {
- using (MemoryStream resultStream = new MemoryStream())
- {
- BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
-
- byte transformFeedbackCount = 0;
-
- if (tfd != null)
- {
- transformFeedbackCount = (byte)tfd.Length;
- }
-
- // Header
- resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
-
- // Write all entries header
- foreach (GuestShaderCacheEntryHeader entry in shaderCacheEntries)
- {
- resultStreamWriter.WriteStruct(entry);
- }
-
- // Finally, write all program code and all transform feedback information.
- resultStreamWriter.Write(programCode);
-
- return resultStream.ToArray();
- }
- }
-
- ///
- /// Write transform feedback guest information to the given stream.
- ///
- /// The stream to write data to
- /// The current transform feedback descriptors used
- private static void WriteTransformationFeedbackInformation(Stream stream, TransformFeedbackDescriptor[] tfd)
- {
- if (tfd != null)
- {
- BinaryWriter writer = new BinaryWriter(stream);
-
- foreach (TransformFeedbackDescriptor transform in tfd)
- {
- writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
- writer.Write(transform.VaryingLocations);
- }
- }
- }
-
- ///
- /// Read transform feedback descriptors from guest.
- ///
- /// The raw guest transform feedback descriptors
- /// The guest shader program header
- /// The transform feedback descriptors read from guest
- private static TransformFeedbackDescriptor[] ReadTransformationFeedbackInformations(ref ReadOnlySpan data, GuestShaderCacheHeader header)
- {
- if (header.TransformFeedbackCount != 0)
- {
- TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
-
- for (int i = 0; i < result.Length; i++)
- {
- GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read(data);
-
- result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf(), feedbackHeader.VaryingLocationsLength).ToArray());
-
- data = data.Slice(Unsafe.SizeOf() + feedbackHeader.VaryingLocationsLength);
- }
-
- return result;
- }
-
- return null;
- }
-
- ///
- /// Create a new instance of from an gpu accessor.
- ///
- /// The gpu accessor
- /// a new instance of
- private static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
- {
- return new GuestGpuAccessorHeader
- {
- ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
- ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
- ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
- ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
- ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
- PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
- };
- }
-
- ///
- /// Write the guest GpuAccessor informations to the given stream.
- ///
- /// The stream to write the guest GpuAcessor
- /// The shader tranlator context in use
- /// The guest gpu accessor header
- private static GuestGpuAccessorHeader WriteGuestGpuAccessorCache(Stream stream, TranslatorContext shaderContext)
- {
- BinaryWriter writer = new BinaryWriter(stream);
-
- GuestGpuAccessorHeader header = CreateGuestGpuAccessorCache(shaderContext.GpuAccessor);
-
- // If we have a full gpu accessor, cache textures descriptors
- if (shaderContext.GpuAccessor is GpuAccessor gpuAccessor)
- {
- HashSet textureHandlesInUse = shaderContext.TextureHandlesForCache;
-
- header.TextureDescriptorCount = textureHandlesInUse.Count;
-
- foreach (int textureHandle in textureHandlesInUse)
- {
- GuestTextureDescriptor textureDescriptor = ((Image.TextureDescriptor)gpuAccessor.GetTextureDescriptor(textureHandle)).ToCache();
-
- textureDescriptor.Handle = (uint)textureHandle;
-
- writer.WriteStruct(textureDescriptor);
- }
- }
-
- return header;
- }
-
- ///
- /// Get the shader program information for use on the shader cache.
- ///
- /// The current transform feedback descriptors used
- /// The shader translators context in use
- /// The resulting raw shader program code
- /// The resulting raw shader program code hash
- /// The resulting guest shader entries header
- private void GetProgramInformations(TransformFeedbackDescriptor[] tfd, ReadOnlySpan shaderContexts, out byte[] programCode, out Hash128 programCodeHash, out GuestShaderCacheEntryHeader[] entries)
- {
- GuestShaderCacheEntryHeader ComputeStage(Stream stream, TranslatorContext context)
- {
- if (context == null)
- {
- return new GuestShaderCacheEntryHeader();
- }
-
- ReadOnlySpan data = _context.MemoryManager.GetSpan(context.Address, context.Size);
-
- stream.Write(data);
-
- int size = data.Length;
- int sizeA = 0;
-
- if (context.AddressA != 0)
- {
- data = _context.MemoryManager.GetSpan(context.AddressA, context.SizeA);
-
- sizeA = data.Length;
-
- stream.Write(data);
- }
-
- GuestGpuAccessorHeader gpuAccessorHeader = WriteGuestGpuAccessorCache(stream, context);
-
- return new GuestShaderCacheEntryHeader(context.Stage, size, sizeA, gpuAccessorHeader);
- }
-
- entries = new GuestShaderCacheEntryHeader[shaderContexts.Length];
-
- using (MemoryStream stream = new MemoryStream())
- {
- for (int i = 0; i < shaderContexts.Length; i++)
- {
- entries[i] = ComputeStage(stream, shaderContexts[i]);
- }
-
- WriteTransformationFeedbackInformation(stream, tfd);
-
- programCode = stream.ToArray();
- programCodeHash = _cacheManager.ComputeHash(programCode);
- }
- }
}
}
\ No newline at end of file