ryujinx-mirror/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
Mary 57d3296ba4
infra: Migrate to .NET 6 (#2829)
* infra: Migrate to .NET 6

* Rollback version naming change

* Workaround .NET 6 ZipArchive API issues

* ci: Switch to VS 2022 for AppVeyor

CI is now ready for .NET 6

* Suppress WebClient warning in DoUpdateWithMultipleThreads

* Attempt to workaround System.Drawing.Common changes on 6.0.0

* Change keyboard rendering from System.Drawing to ImageSharp

* Make the software keyboard renderer multithreaded

* Bump ImageSharp version to 1.0.4 to fix a bug in Image.Load

* Add fallback fonts to the keyboard renderer

* Fix warnings

* Address caian's comment

* Clean up linux workaround as it's uneeded now

* Update readme

Co-authored-by: Caian Benedicto <caianbene@gmail.com>
2021-11-28 21:24:17 +01:00

175 lines
7.8 KiB
C#

using ICSharpCode.SharpZipLib.Zip;
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;
namespace Ryujinx.Graphics.Gpu.Shader.Cache
{
/// <summary>
/// Class handling shader cache migrations.
/// </summary>
static class CacheMigration
{
/// <summary>
/// Check if the given cache version need to recompute its hash.
/// </summary>
/// <param name="version">The version in use</param>
/// <param name="newVersion">The new version after migration</param>
/// <returns>True if a hash recompute is needed</returns>
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;
}
private class StreamZipEntryDataSource : IStaticDataSource
{
private readonly ZipFile Archive;
private readonly ZipEntry Entry;
public StreamZipEntryDataSource(ZipFile archive, ZipEntry entry)
{
Archive = archive;
Entry = entry;
}
public Stream GetSource()
{
return Archive.GetInputStream(Entry);
}
}
/// <summary>
/// Move a file with the name of a given hash to another in the cache archive.
/// </summary>
/// <param name="archive">The archive in use</param>
/// <param name="oldKey">The old key</param>
/// <param name="newKey">The new key</param>
private static void MoveEntry(ZipFile archive, Hash128 oldKey, Hash128 newKey)
{
ZipEntry oldGuestEntry = archive.GetEntry($"{oldKey}");
if (oldGuestEntry != null)
{
archive.Add(new StreamZipEntryDataSource(archive, oldGuestEntry), $"{newKey}", CompressionMethod.Deflated);
archive.Delete(oldGuestEntry);
}
}
/// <summary>
/// Recompute all the hashes of a given cache.
/// </summary>
/// <param name="guestBaseCacheDirectory">The guest cache directory path</param>
/// <param name="hostBaseCacheDirectory">The host cache directory path</param>
/// <param name="graphicsApi">The graphics api in use</param>
/// <param name="hashType">The hash type in use</param>
/// <param name="newVersion">The version to write in the host and guest manifest after migration</param>
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<Hash128> guestEntries))
{
CacheHelper.TryReadManifestFile(hostManifestPath, graphicsApi, hashType, out _, out HashSet<Hash128> 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);
ZipFile guestArchive = new ZipFile(File.Open(guestArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
ZipFile hostArchive = new ZipFile(File.Open(hostArchivePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None));
CacheHelper.EnsureArchiveUpToDate(guestBaseCacheDirectory, guestArchive, guestEntries);
CacheHelper.EnsureArchiveUpToDate(hostBaseCacheDirectory, hostArchive, hostEntries);
int programIndex = 0;
HashSet<Hash128> newEntries = new HashSet<Hash128>();
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<byte> guestProgramReadOnlySpan = guestProgram;
ReadOnlySpan<GuestShaderCacheEntry> 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.CommitUpdate();
hostArchive.CommitUpdate();
guestArchive.Close();
hostArchive.Close();
}
}
/// <summary>
/// Check and run cache migration if needed.
/// </summary>
/// <param name="baseCacheDirectory">The base path of the cache</param>
/// <param name="graphicsApi">The graphics api in use</param>
/// <param name="hashType">The hash type in use</param>
/// <param name="shaderProvider">The shader provider name of the cache</param>
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);
}
}
}
}
}