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.ReadTransformFeedbackInformation(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");
string guestArchivePath = CacheHelper.GetArchivePath(guestBaseCacheDirectory);
string hostArchivePath = CacheHelper.GetArchivePath(hostBaseCacheDirectory);
bool isReadOnly = CacheHelper.IsArchiveReadOnly(guestArchivePath) || CacheHelper.IsArchiveReadOnly(hostArchivePath);
if (!isReadOnly && CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
{
if (NeedHashRecompute(header.Version, out ulong newVersion))
{
RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
}
}
}
}
}