salieri: Support read-only mode if archive is already opened (#1807)
This improves shader cache resilience when people opens another program that touch the cache.zip.
This commit is contained in:
parent
19d18662ea
commit
6bc2733c17
5 changed files with 124 additions and 25 deletions
|
@ -116,6 +116,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// </summary>
|
||||
private ZipArchive _cacheArchive;
|
||||
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Immutable copy of the hash table.
|
||||
/// </summary>
|
||||
|
@ -167,6 +169,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
_hashType = hashType;
|
||||
_version = version;
|
||||
_hashTable = new HashSet<Hash128>();
|
||||
IsReadOnly = CacheHelper.IsArchiveReadOnly(GetArchivePath());
|
||||
|
||||
Load();
|
||||
|
||||
|
@ -230,6 +233,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <param name="entries">Entries to remove from the manifest</param>
|
||||
public void RemoveManifestEntriesAsync(HashSet<Hash128> entries)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Trying to remove manifest entries on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_fileWriterWorkerQueue.Add(new CacheFileOperationTask
|
||||
{
|
||||
Type = CacheFileOperation.RemoveManifestEntries,
|
||||
|
@ -308,6 +318,20 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
|
||||
string archivePath = GetArchivePath();
|
||||
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in read-only, archiving task skipped.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (CacheHelper.IsArchiveReadOnly(archivePath))
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, $"Cache collection archive in use, archiving task skipped.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Open the zip in read/write.
|
||||
_cacheArchive = ZipFile.Open(archivePath, ZipArchiveMode.Update);
|
||||
|
||||
|
@ -446,6 +470,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <param name="value">The value to cache</param>
|
||||
public void AddValue(ref Hash128 keyHash, byte[] value)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Trying to add {keyHash} on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(value != null);
|
||||
|
||||
bool isAlreadyPresent;
|
||||
|
@ -488,6 +519,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// <param name="value">The value to cache</param>
|
||||
public void ReplaceValue(ref Hash128 keyHash, byte[] value)
|
||||
{
|
||||
if (IsReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Trying to replace {keyHash} on a read-only cache, ignoring.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Assert(value != null);
|
||||
|
||||
// Only queue file change operations
|
||||
|
|
|
@ -496,5 +496,27 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsArchiveReadOnly(string archivePath)
|
||||
{
|
||||
FileInfo info = new FileInfo(archivePath);
|
||||
|
||||
if (!info.Exists)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (FileStream stream = info.Open(FileMode.Open, FileAccess.Read, FileShare.None))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
||||
{
|
||||
|
@ -31,6 +29,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
/// </summary>
|
||||
private const ulong GuestCacheVersion = 1759;
|
||||
|
||||
public bool IsReadOnly => _guestProgramCache.IsReadOnly || _hostProgramCache.IsReadOnly;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new cache manager instance
|
||||
/// </summary>
|
||||
|
|
|
@ -146,7 +146,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache
|
|||
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))
|
||||
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))
|
||||
{
|
||||
|
|
|
@ -61,7 +61,18 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
{
|
||||
_cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
|
||||
|
||||
HashSet<Hash128> invalidEntries = new HashSet<Hash128>();
|
||||
bool isReadOnly = _cacheManager.IsReadOnly;
|
||||
|
||||
HashSet<Hash128> invalidEntries = null;
|
||||
|
||||
if (isReadOnly)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Gpu, "Loading shader cache in read-only mode (cache in use by another program!)");
|
||||
}
|
||||
else
|
||||
{
|
||||
invalidEntries = new HashSet<Hash128>();
|
||||
}
|
||||
|
||||
ReadOnlySpan<Hash128> guestProgramList = _cacheManager.GetGuestProgramList();
|
||||
|
||||
|
@ -84,7 +95,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)");
|
||||
|
||||
// Should not happen, but if someone messed with the cache it's better to catch it.
|
||||
invalidEntries.Add(key);
|
||||
invalidEntries?.Add(key);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -141,6 +152,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
// As the host program was invalidated, save the new entry in the cache.
|
||||
hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
|
||||
|
||||
if (!isReadOnly)
|
||||
{
|
||||
if (hasHostCache)
|
||||
{
|
||||
_cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
|
||||
|
@ -152,6 +165,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
_cacheManager.AddHostProgram(ref key, hostProgramBinary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
|
||||
}
|
||||
|
@ -270,6 +284,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
// As the host program was invalidated, save the new entry in the cache.
|
||||
hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
|
||||
|
||||
if (!isReadOnly)
|
||||
{
|
||||
if (hasHostCache)
|
||||
{
|
||||
_cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
|
||||
|
@ -281,15 +297,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
_cacheManager.AddHostProgram(ref key, hostProgramBinary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isReadOnly)
|
||||
{
|
||||
// Remove entries that are broken in the cache
|
||||
_cacheManager.RemoveManifestEntries(invalidEntries);
|
||||
_cacheManager.FlushToArchive();
|
||||
_cacheManager.Synchronize();
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
|
||||
}
|
||||
|
@ -343,12 +363,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
sharedMemorySize);
|
||||
|
||||
bool isShaderCacheEnabled = _cacheManager != null;
|
||||
bool isShaderCacheReadOnly = false;
|
||||
|
||||
Hash128 programCodeHash = default;
|
||||
GuestShaderCacheEntry[] shaderCacheEntries = null;
|
||||
|
||||
if (isShaderCacheEnabled)
|
||||
{
|
||||
isShaderCacheReadOnly = _cacheManager.IsReadOnly;
|
||||
|
||||
// Compute hash and prepare data for shader disk cache comparison.
|
||||
shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
|
||||
programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries);
|
||||
|
@ -378,9 +401,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
if (isShaderCacheEnabled)
|
||||
{
|
||||
_cpProgramsDiskCache.Add(programCodeHash, cpShader);
|
||||
|
||||
if (!isShaderCacheReadOnly)
|
||||
{
|
||||
_cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCached)
|
||||
{
|
||||
|
@ -447,12 +474,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
shaderContexts[4] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Fragment, addresses.Fragment);
|
||||
|
||||
bool isShaderCacheEnabled = _cacheManager != null;
|
||||
bool isShaderCacheReadOnly = false;
|
||||
|
||||
Hash128 programCodeHash = default;
|
||||
GuestShaderCacheEntry[] shaderCacheEntries = null;
|
||||
|
||||
if (isShaderCacheEnabled)
|
||||
{
|
||||
isShaderCacheReadOnly = _cacheManager.IsReadOnly;
|
||||
|
||||
// Compute hash and prepare data for shader disk cache comparison.
|
||||
shaderCacheEntries = CacheHelper.CreateShaderCacheEntries(_context.MemoryManager, shaderContexts);
|
||||
programCodeHash = CacheHelper.ComputeGuestHashFromCache(shaderCacheEntries, tfd);
|
||||
|
@ -504,9 +534,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||
if (isShaderCacheEnabled)
|
||||
{
|
||||
_gpProgramsDiskCache.Add(programCodeHash, gpShaders);
|
||||
|
||||
if (!isShaderCacheReadOnly)
|
||||
{
|
||||
_cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isCached)
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue