mirror of
https://github.com/GreemDev/Ryujinx.git
synced 2024-12-01 04:57:18 +01:00
salieri: Fix missing guest GPU accessor missing on hashes (#1759)
This adds the guest GPU accessor to hashes computation. As this change all the hashes from the cache, I added some migration logic. This is required for #1755.
This commit is contained in:
parent
5e6dc37aed
commit
f6d88558b1
@ -125,26 +125,26 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
/// Get the temp path to the cache data directory.
|
/// Get the temp path to the cache data directory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The temp path to the cache data directory</returns>
|
/// <returns>The temp path to the cache data directory</returns>
|
||||||
private string GetCacheTempDataPath() => Path.Combine(_cacheDirectory, "temp");
|
private string GetCacheTempDataPath() => CacheHelper.GetCacheTempDataPath(_cacheDirectory);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path to the cache archive file.
|
/// The path to the cache archive file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The path to the cache archive file</returns>
|
/// <returns>The path to the cache archive file</returns>
|
||||||
private string GetArchivePath() => Path.Combine(_cacheDirectory, "cache.zip");
|
private string GetArchivePath() => CacheHelper.GetArchivePath(_cacheDirectory);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The path to the cache manifest file.
|
/// The path to the cache manifest file.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The path to the cache manifest file</returns>
|
/// <returns>The path to the cache manifest file</returns>
|
||||||
private string GetManifestPath() => Path.Combine(_cacheDirectory, "cache.info");
|
private string GetManifestPath() => CacheHelper.GetManifestPath(_cacheDirectory);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new temp path to the given cached file via its hash.
|
/// Create a new temp path to the given cached file via its hash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="key">The hash of the cached data</param>
|
/// <param name="key">The hash of the cached data</param>
|
||||||
/// <returns>New path to the given cached file</returns>
|
/// <returns>New path to the given cached file</returns>
|
||||||
private string GenCacheTempFilePath(Hash128 key) => Path.Combine(GetCacheTempDataPath(), key.ToString());
|
private string GenCacheTempFilePath(Hash128 key) => CacheHelper.GenCacheTempFilePath(_cacheDirectory, key);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new cache collection.
|
/// Create a new cache collection.
|
||||||
@ -162,7 +162,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
throw new NotImplementedException($"{hashType}");
|
throw new NotImplementedException($"{hashType}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheDirectory = GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
|
_cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
|
||||||
_graphicsApi = graphicsApi;
|
_graphicsApi = graphicsApi;
|
||||||
_hashType = hashType;
|
_hashType = hashType;
|
||||||
_version = version;
|
_version = version;
|
||||||
@ -178,13 +178,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void Load()
|
private void Load()
|
||||||
{
|
{
|
||||||
bool isInvalid = false;
|
bool isValid = false;
|
||||||
|
|
||||||
if (!Directory.Exists(_cacheDirectory))
|
if (Directory.Exists(_cacheDirectory))
|
||||||
{
|
|
||||||
isInvalid = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
string manifestPath = GetManifestPath();
|
string manifestPath = GetManifestPath();
|
||||||
|
|
||||||
@ -196,9 +192,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
{
|
{
|
||||||
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
||||||
|
|
||||||
isInvalid = !manifestHeader.IsValid(_version, _graphicsApi, _hashType, hashTableRaw.Span);
|
isValid = manifestHeader.IsValid(_graphicsApi, _hashType, hashTableRaw.Span) && _version == manifestHeader.Version;
|
||||||
|
|
||||||
if (!isInvalid)
|
if (isValid)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
|
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(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.");
|
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.
|
// Update the content of the zip.
|
||||||
lock (_hashTable)
|
lock (_hashTable)
|
||||||
{
|
{
|
||||||
foreach (Hash128 hash in _hashTable)
|
CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close the instance to force a flush.
|
// Close the instance to force a flush.
|
||||||
_cacheArchive.Dispose();
|
_cacheArchive.Dispose();
|
||||||
@ -362,56 +339,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void SaveManifest()
|
private void SaveManifest()
|
||||||
{
|
{
|
||||||
CacheManifestHeader manifestHeader = new CacheManifestHeader(_version, _graphicsApi, _hashType);
|
|
||||||
|
|
||||||
byte[] data;
|
byte[] data;
|
||||||
|
|
||||||
lock (_hashTable)
|
lock (_hashTable)
|
||||||
{
|
{
|
||||||
data = new byte[Unsafe.SizeOf<CacheManifestHeader>() + _hashTable.Count * Unsafe.SizeOf<Hash128>()];
|
data = CacheHelper.ComputeManifest(_version, _graphicsApi, _hashType, _hashTable);
|
||||||
|
|
||||||
// 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 _hashTable)
|
|
||||||
{
|
|
||||||
dataSpan[i++] = hash;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf<CacheManifestHeader>()));
|
|
||||||
|
|
||||||
MemoryMarshal.Write(data, ref manifestHeader);
|
|
||||||
|
|
||||||
File.WriteAllBytes(GetManifestPath(), data);
|
File.WriteAllBytes(GetManifestPath(), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <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>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a cached file with the given hash.
|
/// Get a cached file with the given hash.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -438,27 +375,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
|
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry archiveEntry = _cacheArchive.GetEntry($"{keyHash}");
|
return CacheHelper.ReadFromArchive(_cacheArchive, 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 null;
|
return null;
|
||||||
@ -480,17 +397,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
|
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
string cacheTempFilePath = GenCacheTempFilePath(keyHash);
|
return CacheHelper.ReadFromFile(GetCacheTempDataPath(), 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 null;
|
return null;
|
||||||
|
482
Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
Normal file
482
Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs
Normal file
@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper to manipulate the disk shader cache.
|
||||||
|
/// </summary>
|
||||||
|
static class CacheHelper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Try to read the manifest header from a given file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manifestPath">The path to the manifest file</param>
|
||||||
|
/// <param name="header">The manifest header read</param>
|
||||||
|
/// <returns>Return true if the manifest header was read</returns>
|
||||||
|
public static bool TryReadManifestHeader(string manifestPath, out CacheManifestHeader header)
|
||||||
|
{
|
||||||
|
header = default;
|
||||||
|
|
||||||
|
if (File.Exists(manifestPath))
|
||||||
|
{
|
||||||
|
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||||
|
|
||||||
|
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Try to read the manifest from a given file path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="manifestPath">The path to the manifest file</param>
|
||||||
|
/// <param name="graphicsApi">The graphics api used by the cache</param>
|
||||||
|
/// <param name="hashType">The hash type of the cache</param>
|
||||||
|
/// <param name="header">The manifest header read</param>
|
||||||
|
/// <param name="entries">The entries read from the cache manifest</param>
|
||||||
|
/// <returns>Return true if the manifest was read</returns>
|
||||||
|
public static bool TryReadManifestFile(string manifestPath, CacheGraphicsApi graphicsApi, CacheHashType hashType, out CacheManifestHeader header, out HashSet<Hash128> entries)
|
||||||
|
{
|
||||||
|
header = default;
|
||||||
|
entries = new HashSet<Hash128>();
|
||||||
|
|
||||||
|
if (File.Exists(manifestPath))
|
||||||
|
{
|
||||||
|
Memory<byte> rawManifest = File.ReadAllBytes(manifestPath);
|
||||||
|
|
||||||
|
if (MemoryMarshal.TryRead(rawManifest.Span, out header))
|
||||||
|
{
|
||||||
|
Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>());
|
||||||
|
|
||||||
|
bool isValid = header.IsValid(graphicsApi, hashType, hashTableRaw.Span);
|
||||||
|
|
||||||
|
if (isValid)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span);
|
||||||
|
|
||||||
|
foreach (Hash128 hash in hashTable)
|
||||||
|
{
|
||||||
|
entries.Add(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isValid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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().Slice(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(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>
|
||||||
|
/// Compute the guest program code for usage while dumping to disk or hash.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||||
|
/// <param name="tfd">The transform feedback descriptors</param>
|
||||||
|
/// <param name="forHashCompute">Used to determine if the guest program code is generated for hashing</param>
|
||||||
|
/// <returns>The guest program code for usage while dumping to disk or hash</returns>
|
||||||
|
private static byte[] ComputeGuestProgramCode(ReadOnlySpan<GuestShaderCacheEntry> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compute a guest hash from shader entries.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="cachedShaderEntries">The guest shader entries to use</param>
|
||||||
|
/// <param name="tfd">The optional transform feedback descriptors</param>
|
||||||
|
/// <returns>A guest hash from shader entries</returns>
|
||||||
|
public static Hash128 ComputeGuestHashFromCache(ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries, TransformFeedbackDescriptor[] tfd = null)
|
||||||
|
{
|
||||||
|
return XXHash128.ComputeHash(ComputeGuestProgramCode(cachedShaderEntries, tfd, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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 TransformFeedbackDescriptor[] ReadTransformationFeedbackInformations(ref ReadOnlySpan<byte> 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<GuestShaderCacheTransformFeedbackHeader>(data);
|
||||||
|
|
||||||
|
result[i] = new TransformFeedbackDescriptor(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>
|
||||||
|
/// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="gpuAccessor">The gpu accessor</param>
|
||||||
|
/// <returns>A new instance of <see cref="GuestGpuAccessorHeader"/></returns>
|
||||||
|
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(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create guest shader cache entries from the runtime contexts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="memoryManager">The GPU memory manager in use</param>
|
||||||
|
/// <param name="shaderContexts">The runtime contexts</param>
|
||||||
|
/// <returns>Guest shader cahe entries from the runtime contexts</returns>
|
||||||
|
public static GuestShaderCacheEntry[] CreateShaderCacheEntries(MemoryManager memoryManager, ReadOnlySpan<TranslatorContext> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a guest shader program.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shaderCacheEntries">The entries composing the guest program dump</param>
|
||||||
|
/// <param name="tfd">The transform feedback descriptors in use</param>
|
||||||
|
/// <returns>The resulting guest shader program</returns>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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, ZipArchive archive, HashSet<Hash128> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the guest cache shader (to increment when guest cache structure change).
|
/// Version of the guest cache shader (to increment when guest cache structure change).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const ulong GuestCacheVersion = 1717;
|
private const ulong GuestCacheVersion = 1759;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a new cache manager instance
|
/// Create a new cache manager instance
|
||||||
@ -45,7 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
_hashType = hashType;
|
_hashType = hashType;
|
||||||
_shaderProvider = shaderProvider;
|
_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);
|
_guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
|
||||||
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
|
_hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
|
||||||
@ -80,16 +82,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||||||
_hostProgramCache.Synchronize();
|
_hostProgramCache.Synchronize();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Computes the hash of some data using the current cache hashing algorithm.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="data">Some data to generate a hash for.</param>
|
|
||||||
/// <returns>The hash of some data using the current hashing algorithm of the cache</returns>
|
|
||||||
public Hash128 ComputeHash(ReadOnlySpan<byte> data)
|
|
||||||
{
|
|
||||||
return XXHash128.ComputeHash(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Save a shader program not present in the program cache.
|
/// Save a shader program not present in the program cache.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
158
Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
Normal file
158
Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs
Normal file
@ -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
|
||||||
|
{
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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(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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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);
|
||||||
|
|
||||||
|
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<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.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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");
|
||||||
|
|
||||||
|
if (CacheHelper.TryReadManifestHeader(CacheHelper.GetManifestPath(guestBaseCacheDirectory), out CacheManifestHeader header))
|
||||||
|
{
|
||||||
|
if (NeedHashRecompute(header.Version, out ulong newVersion))
|
||||||
|
{
|
||||||
|
RecomputeHashes(guestBaseCacheDirectory, hostBaseCacheDirectory, graphicsApi, hashType, newVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -84,14 +84,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Check the validity of the header.
|
/// Check the validity of the header.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="version">The target version in use</param>
|
|
||||||
/// <param name="graphicsApi">The target graphics api in use</param>
|
/// <param name="graphicsApi">The target graphics api in use</param>
|
||||||
/// <param name="hashType">The target hash type in use</param>
|
/// <param name="hashType">The target hash type in use</param>
|
||||||
/// <param name="data">The data after this header</param>
|
/// <param name="data">The data after this header</param>
|
||||||
/// <returns>True if the header is valid</returns>
|
/// <returns>True if the header is valid</returns>
|
||||||
public bool IsValid(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan<byte> data)
|
/// <remarks>This doesn't check that versions match</remarks>
|
||||||
|
public bool IsValid(CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan<byte> data)
|
||||||
{
|
{
|
||||||
return Version == version && GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
|
return GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="header">The header of the cached shader entry</param>
|
/// <param name="header">The header of the cached shader entry</param>
|
||||||
/// <param name="code">The code of this shader</param>
|
/// <param name="code">The code of this shader</param>
|
||||||
private GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
|
public GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
|
||||||
{
|
{
|
||||||
Header = header;
|
Header = header;
|
||||||
Code = code;
|
Code = code;
|
||||||
|
@ -9,9 +9,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Shader
|
namespace Ryujinx.Graphics.Gpu.Shader
|
||||||
{
|
{
|
||||||
@ -37,7 +34,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Version of the codegen (to be changed when codegen or guest format change).
|
/// Version of the codegen (to be changed when codegen or guest format change).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const ulong ShaderCodeGenVersion = 1717;
|
private const ulong ShaderCodeGenVersion = 1759;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of the shader cache.
|
/// Creates a new instance of the shader cache.
|
||||||
@ -165,7 +162,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
|
ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
|
||||||
List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
|
List<ShaderProgram> shaderPrograms = new List<ShaderProgram>();
|
||||||
|
|
||||||
TransformFeedbackDescriptor[] tfd = ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader);
|
TransformFeedbackDescriptor[] tfd = CacheHelper.ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader);
|
||||||
|
|
||||||
TranslationFlags flags = DefaultFlags;
|
TranslationFlags flags = DefaultFlags;
|
||||||
|
|
||||||
@ -347,14 +344,14 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
|
|
||||||
bool isShaderCacheEnabled = _cacheManager != null;
|
bool isShaderCacheEnabled = _cacheManager != null;
|
||||||
|
|
||||||
byte[] programCode = null;
|
|
||||||
Hash128 programCodeHash = default;
|
Hash128 programCodeHash = default;
|
||||||
GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
|
GuestShaderCacheEntry[] shaderCacheEntries = null;
|
||||||
|
|
||||||
if (isShaderCacheEnabled)
|
if (isShaderCacheEnabled)
|
||||||
{
|
{
|
||||||
// Compute hash and prepare data for shader disk cache comparison.
|
// 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;
|
ShaderBundle cpShader;
|
||||||
@ -381,7 +378,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
if (isShaderCacheEnabled)
|
if (isShaderCacheEnabled)
|
||||||
{
|
{
|
||||||
_cpProgramsDiskCache.Add(programCodeHash, cpShader);
|
_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;
|
bool isShaderCacheEnabled = _cacheManager != null;
|
||||||
|
|
||||||
byte[] programCode = null;
|
|
||||||
Hash128 programCodeHash = default;
|
Hash128 programCodeHash = default;
|
||||||
GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
|
GuestShaderCacheEntry[] shaderCacheEntries = null;
|
||||||
|
|
||||||
if (isShaderCacheEnabled)
|
if (isShaderCacheEnabled)
|
||||||
{
|
{
|
||||||
// Compute hash and prepare data for shader disk cache comparison.
|
// 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;
|
ShaderBundle gpShaders;
|
||||||
@ -507,7 +504,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
if (isShaderCacheEnabled)
|
if (isShaderCacheEnabled)
|
||||||
{
|
{
|
||||||
_gpProgramsDiskCache.Add(programCodeHash, gpShaders);
|
_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();
|
_cacheManager?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create a guest shader program.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="programCode">The program code of the shader code</param>
|
|
||||||
/// <param name="shaderCacheEntries">The resulting guest shader entries header</param>
|
|
||||||
/// <param name="tfd">The transform feedback descriptors in use</param>
|
|
||||||
/// <returns>The resulting guest shader program</returns>
|
|
||||||
private static byte[] CreateGuestProgramDump(ReadOnlySpan<byte> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write transform feedback guest information to the given stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">The stream to write data to</param>
|
|
||||||
/// <param name="tfd">The current transform feedback descriptors used</param>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
private static TransformFeedbackDescriptor[] ReadTransformationFeedbackInformations(ref ReadOnlySpan<byte> 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<GuestShaderCacheTransformFeedbackHeader>(data);
|
|
||||||
|
|
||||||
result[i] = new TransformFeedbackDescriptor(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>
|
|
||||||
/// Create a new instance of <see cref="GuestGpuAccessorHeader"/> from an gpu accessor.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="gpuAccessor">The gpu accessor</param>
|
|
||||||
/// <returns>a new instance of <see cref="GuestGpuAccessorHeader"/></returns>
|
|
||||||
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(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Write the guest GpuAccessor informations to the given stream.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stream">The stream to write the guest GpuAcessor</param>
|
|
||||||
/// <param name="shaderContext">The shader tranlator context in use</param>
|
|
||||||
/// <returns>The guest gpu accessor header</returns>
|
|
||||||
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<int> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get the shader program information for use on the shader cache.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="tfd">The current transform feedback descriptors used</param>
|
|
||||||
/// <param name="shaderContexts">The shader translators context in use</param>
|
|
||||||
/// <param name="programCode">The resulting raw shader program code</param>
|
|
||||||
/// <param name="programCodeHash">The resulting raw shader program code hash</param>
|
|
||||||
/// <param name="entries">The resulting guest shader entries header</param>
|
|
||||||
private void GetProgramInformations(TransformFeedbackDescriptor[] tfd, ReadOnlySpan<TranslatorContext> shaderContexts, out byte[] programCode, out Hash128 programCodeHash, out GuestShaderCacheEntryHeader[] entries)
|
|
||||||
{
|
|
||||||
GuestShaderCacheEntryHeader ComputeStage(Stream stream, TranslatorContext context)
|
|
||||||
{
|
|
||||||
if (context == null)
|
|
||||||
{
|
|
||||||
return new GuestShaderCacheEntryHeader();
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user