diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs index 9b61ef4a6..2660e5286 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs @@ -116,6 +116,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache /// private ZipArchive _cacheArchive; + public bool IsReadOnly { get; } + /// /// Immutable copy of the hash table. /// @@ -167,6 +169,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache _hashType = hashType; _version = version; _hashTable = new HashSet(); + IsReadOnly = CacheHelper.IsArchiveReadOnly(GetArchivePath()); Load(); @@ -230,6 +233,13 @@ namespace Ryujinx.Graphics.Gpu.Shader.Cache /// Entries to remove from the manifest public void RemoveManifestEntriesAsync(HashSet 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 /// The value to cache 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 /// The value to cache 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 diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs index d10e46711..d109f1cd6 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs @@ -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; + } + } } } diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs index ca0070fdc..1ac37704a 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs @@ -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 /// private const ulong GuestCacheVersion = 1759; + public bool IsReadOnly => _guestProgramCache.IsReadOnly || _hostProgramCache.IsReadOnly; + /// /// Create a new cache manager instance /// diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs index 965287b54..839853c07 100644 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs +++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheMigration.cs @@ -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)) { diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index a04affc20..d28d73620 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -61,7 +61,18 @@ namespace Ryujinx.Graphics.Gpu.Shader { _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); - HashSet invalidEntries = new HashSet(); + bool isReadOnly = _cacheManager.IsReadOnly; + + HashSet 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(); + } ReadOnlySpan 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,15 +152,18 @@ 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 (hasHostCache) + if (!isReadOnly) { - _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); - } - else - { - Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); + if (hasHostCache) + { + _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); - _cacheManager.AddHostProgram(ref key, hostProgramBinary); + _cacheManager.AddHostProgram(ref key, hostProgramBinary); + } } } @@ -270,15 +284,18 @@ 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 (hasHostCache) + if (!isReadOnly) { - _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); - } - else - { - Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); + if (hasHostCache) + { + _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary); + } + else + { + Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)"); - _cacheManager.AddHostProgram(ref key, hostProgramBinary); + _cacheManager.AddHostProgram(ref key, hostProgramBinary); + } } } @@ -286,10 +303,13 @@ namespace Ryujinx.Graphics.Gpu.Shader } } - // Remove entries that are broken in the cache - _cacheManager.RemoveManifestEntries(invalidEntries); - _cacheManager.FlushToArchive(); - _cacheManager.Synchronize(); + 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,7 +401,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (isShaderCacheEnabled) { _cpProgramsDiskCache.Add(programCodeHash, cpShader); - _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary); + + if (!isShaderCacheReadOnly) + { + _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries), hostProgramBinary); + } } } @@ -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,7 +534,11 @@ namespace Ryujinx.Graphics.Gpu.Shader if (isShaderCacheEnabled) { _gpProgramsDiskCache.Add(programCodeHash, gpShaders); - _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary); + + if (!isShaderCacheReadOnly) + { + _cacheManager.SaveProgram(ref programCodeHash, CacheHelper.CreateGuestProgramDump(shaderCacheEntries, tfd), hostProgramBinary); + } } }