diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader')
30 files changed, 783 insertions, 2450 deletions
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs deleted file mode 100644 index a98531f6..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs +++ /dev/null @@ -1,617 +0,0 @@ -using ICSharpCode.SharpZipLib.Zip; -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache -{ - /// <summary> - /// Represent a cache collection handling one shader cache. - /// </summary> - class CacheCollection : IDisposable - { - /// <summary> - /// Possible operation to do on the <see cref="_fileWriterWorkerQueue"/>. - /// </summary> - private enum CacheFileOperation - { - /// <summary> - /// Save a new entry in the temp cache. - /// </summary> - SaveTempEntry, - - /// <summary> - /// Save the hash manifest. - /// </summary> - SaveManifest, - - /// <summary> - /// Remove entries from the hash manifest and save it. - /// </summary> - RemoveManifestEntries, - - /// <summary> - /// Remove entries from the hash manifest and save it, and also deletes the temporary file. - /// </summary> - RemoveManifestEntryAndTempFile, - - /// <summary> - /// Flush temporary cache to archive. - /// </summary> - FlushToArchive, - - /// <summary> - /// Signal when hitting this point. This is useful to know if all previous operations were performed. - /// </summary> - Synchronize - } - - /// <summary> - /// Represent an operation to perform on the <see cref="_fileWriterWorkerQueue"/>. - /// </summary> - private class CacheFileOperationTask - { - /// <summary> - /// The type of operation to perform. - /// </summary> - public CacheFileOperation Type; - - /// <summary> - /// The data associated to this operation or null. - /// </summary> - public object Data; - } - - /// <summary> - /// Data associated to the <see cref="CacheFileOperation.SaveTempEntry"/> operation. - /// </summary> - private class CacheFileSaveEntryTaskData - { - /// <summary> - /// The key of the entry to cache. - /// </summary> - public Hash128 Key; - - /// <summary> - /// The value of the entry to cache. - /// </summary> - public byte[] Value; - } - - /// <summary> - /// The directory of the shader cache. - /// </summary> - private readonly string _cacheDirectory; - - /// <summary> - /// The version of the cache. - /// </summary> - private readonly ulong _version; - - /// <summary> - /// The hash type of the cache. - /// </summary> - private readonly CacheHashType _hashType; - - /// <summary> - /// The graphics API of the cache. - /// </summary> - private readonly CacheGraphicsApi _graphicsApi; - - /// <summary> - /// The table of all the hash registered in the cache. - /// </summary> - private HashSet<Hash128> _hashTable; - - /// <summary> - /// The queue of operations to be performed by the file writer worker. - /// </summary> - private AsyncWorkQueue<CacheFileOperationTask> _fileWriterWorkerQueue; - - /// <summary> - /// Main storage of the cache collection. - /// </summary> - private ZipFile _cacheArchive; - - /// <summary> - /// Indicates if the cache collection supports modification. - /// </summary> - public bool IsReadOnly { get; } - - /// <summary> - /// Immutable copy of the hash table. - /// </summary> - public ReadOnlySpan<Hash128> HashTable => _hashTable.ToArray(); - - /// <summary> - /// Get the temp path to the cache data directory. - /// </summary> - /// <returns>The temp path to the cache data directory</returns> - private string GetCacheTempDataPath() => CacheHelper.GetCacheTempDataPath(_cacheDirectory); - - /// <summary> - /// The path to the cache archive file. - /// </summary> - /// <returns>The path to the cache archive file</returns> - private string GetArchivePath() => CacheHelper.GetArchivePath(_cacheDirectory); - - /// <summary> - /// The path to the cache manifest file. - /// </summary> - /// <returns>The path to the cache manifest file</returns> - private string GetManifestPath() => CacheHelper.GetManifestPath(_cacheDirectory); - - /// <summary> - /// Create a new temp path to the given cached file via its hash. - /// </summary> - /// <param name="key">The hash of the cached data</param> - /// <returns>New path to the given cached file</returns> - private string GenCacheTempFilePath(Hash128 key) => CacheHelper.GenCacheTempFilePath(_cacheDirectory, key); - - /// <summary> - /// Create a new cache collection. - /// </summary> - /// <param name="baseCacheDirectory">The directory of the shader cache</param> - /// <param name="hashType">The hash type of the shader cache</param> - /// <param name="graphicsApi">The graphics api of the shader cache</param> - /// <param name="shaderProvider">The shader provider name of the shader cache</param> - /// <param name="cacheName">The name of the cache</param> - /// <param name="version">The version of the cache</param> - public CacheCollection(string baseCacheDirectory, CacheHashType hashType, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName, ulong version) - { - if (hashType != CacheHashType.XxHash128) - { - throw new NotImplementedException($"{hashType}"); - } - - _cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName); - _graphicsApi = graphicsApi; - _hashType = hashType; - _version = version; - _hashTable = new HashSet<Hash128>(); - IsReadOnly = CacheHelper.IsArchiveReadOnly(GetArchivePath()); - - Load(); - - _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(HandleCacheTask, $"CacheCollection.Worker.{cacheName}"); - } - - /// <summary> - /// Load the cache manifest file and recreate it if invalid. - /// </summary> - private void Load() - { - bool isValid = false; - - if (Directory.Exists(_cacheDirectory)) - { - string manifestPath = GetManifestPath(); - - if (File.Exists(manifestPath)) - { - Memory<byte> rawManifest = File.ReadAllBytes(manifestPath); - - if (MemoryMarshal.TryRead(rawManifest.Span, out CacheManifestHeader manifestHeader)) - { - Memory<byte> hashTableRaw = rawManifest.Slice(Unsafe.SizeOf<CacheManifestHeader>()); - - isValid = manifestHeader.IsValid(_graphicsApi, _hashType, hashTableRaw.Span) && _version == manifestHeader.Version; - - if (isValid) - { - ReadOnlySpan<Hash128> hashTable = MemoryMarshal.Cast<byte, Hash128>(hashTableRaw.Span); - - foreach (Hash128 hash in hashTable) - { - _hashTable.Add(hash); - } - } - } - } - } - - if (!isValid) - { - Logger.Warning?.Print(LogClass.Gpu, $"Shader collection \"{_cacheDirectory}\" got invalidated, cache will need to be rebuilt."); - - if (Directory.Exists(_cacheDirectory)) - { - Directory.Delete(_cacheDirectory, true); - } - - Directory.CreateDirectory(_cacheDirectory); - - SaveManifest(); - } - - FlushToArchive(); - } - - /// <summary> - /// Queue a task to remove entries from the hash manifest. - /// </summary> - /// <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, - Data = entries - }); - } - - /// <summary> - /// Remove given entries from the manifest. - /// </summary> - /// <param name="entries">Entries to remove from the manifest</param> - private void RemoveManifestEntries(HashSet<Hash128> entries) - { - lock (_hashTable) - { - foreach (Hash128 entry in entries) - { - _hashTable.Remove(entry); - } - - SaveManifest(); - } - } - - /// <summary> - /// Remove given entry from the manifest and delete the temporary file. - /// </summary> - /// <param name="entry">Entry to remove from the manifest</param> - private void RemoveManifestEntryAndTempFile(Hash128 entry) - { - lock (_hashTable) - { - _hashTable.Remove(entry); - SaveManifest(); - } - - File.Delete(GenCacheTempFilePath(entry)); - } - - /// <summary> - /// Queue a task to flush temporary files to the archive on the worker. - /// </summary> - public void FlushToArchiveAsync() - { - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.FlushToArchive - }); - } - - /// <summary> - /// Wait for all tasks before this given point to be done. - /// </summary> - public void Synchronize() - { - using (ManualResetEvent evnt = new ManualResetEvent(false)) - { - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.Synchronize, - Data = evnt - }); - - evnt.WaitOne(); - } - } - - /// <summary> - /// Flush temporary files to the archive. - /// </summary> - /// <remarks>This dispose <see cref="_cacheArchive"/> if not null and reinstantiate it.</remarks> - private void FlushToArchive() - { - EnsureArchiveUpToDate(); - - // Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations. - _cacheArchive = new ZipFile(File.OpenRead(GetArchivePath())); - } - - /// <summary> - /// Save temporary files not in archive. - /// </summary> - /// <remarks>This dispose <see cref="_cacheArchive"/> if not null.</remarks> - public void EnsureArchiveUpToDate() - { - // First close previous opened instance if found. - if (_cacheArchive != null) - { - _cacheArchive.Close(); - } - - 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; - } - - if (!File.Exists(archivePath)) - { - using (ZipFile newZip = ZipFile.Create(archivePath)) - { - // Workaround for SharpZipLib issue #395 - newZip.BeginUpdate(); - newZip.CommitUpdate(); - } - } - - // Open the zip in read/write. - _cacheArchive = new ZipFile(File.Open(archivePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)); - - Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}..."); - - // Update the content of the zip. - lock (_hashTable) - { - CacheHelper.EnsureArchiveUpToDate(_cacheDirectory, _cacheArchive, _hashTable); - - // Close the instance to force a flush. - _cacheArchive.Close(); - _cacheArchive = null; - - string cacheTempDataPath = GetCacheTempDataPath(); - - // Create the cache data path if missing. - if (!Directory.Exists(cacheTempDataPath)) - { - Directory.CreateDirectory(cacheTempDataPath); - } - } - - Logger.Info?.Print(LogClass.Gpu, $"Updated cache collection archive {archivePath}."); - } - - /// <summary> - /// Save the manifest file. - /// </summary> - private void SaveManifest() - { - byte[] data; - - lock (_hashTable) - { - data = CacheHelper.ComputeManifest(_version, _graphicsApi, _hashType, _hashTable); - } - - File.WriteAllBytes(GetManifestPath(), data); - } - - /// <summary> - /// Get a cached file with the given hash. - /// </summary> - /// <param name="keyHash">The given hash</param> - /// <returns>The cached file if present or null</returns> - public byte[] GetValueRaw(ref Hash128 keyHash) - { - return GetValueRawFromArchive(ref keyHash) ?? GetValueRawFromFile(ref keyHash); - } - - /// <summary> - /// Get a cached file with the given hash that is present in the archive. - /// </summary> - /// <param name="keyHash">The given hash</param> - /// <returns>The cached file if present or null</returns> - private byte[] GetValueRawFromArchive(ref Hash128 keyHash) - { - bool found; - - lock (_hashTable) - { - found = _hashTable.Contains(keyHash); - } - - if (found) - { - return CacheHelper.ReadFromArchive(_cacheArchive, keyHash); - } - - return null; - } - - /// <summary> - /// Get a cached file with the given hash that is not present in the archive. - /// </summary> - /// <param name="keyHash">The given hash</param> - /// <returns>The cached file if present or null</returns> - private byte[] GetValueRawFromFile(ref Hash128 keyHash) - { - bool found; - - lock (_hashTable) - { - found = _hashTable.Contains(keyHash); - } - - if (found) - { - return CacheHelper.ReadFromFile(GetCacheTempDataPath(), keyHash); - } - - return null; - } - - private void HandleCacheTask(CacheFileOperationTask task) - { - switch (task.Type) - { - case CacheFileOperation.SaveTempEntry: - SaveTempEntry((CacheFileSaveEntryTaskData)task.Data); - break; - case CacheFileOperation.SaveManifest: - SaveManifest(); - break; - case CacheFileOperation.RemoveManifestEntries: - RemoveManifestEntries((HashSet<Hash128>)task.Data); - break; - case CacheFileOperation.RemoveManifestEntryAndTempFile: - RemoveManifestEntryAndTempFile((Hash128)task.Data); - break; - case CacheFileOperation.FlushToArchive: - FlushToArchive(); - break; - case CacheFileOperation.Synchronize: - ((ManualResetEvent)task.Data).Set(); - break; - default: - throw new NotImplementedException($"{task.Type}"); - } - - } - - /// <summary> - /// Save a new entry in the temp cache. - /// </summary> - /// <param name="entry">The entry to save in the temp cache</param> - private void SaveTempEntry(CacheFileSaveEntryTaskData entry) - { - string tempPath = GenCacheTempFilePath(entry.Key); - - File.WriteAllBytes(tempPath, entry.Value); - } - - /// <summary> - /// Add a new value in the cache with a given hash. - /// </summary> - /// <param name="keyHash">The hash to use for the value in the cache</param> - /// <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; - - lock (_hashTable) - { - isAlreadyPresent = !_hashTable.Add(keyHash); - } - - if (isAlreadyPresent) - { - // NOTE: Used for debug - File.WriteAllBytes(GenCacheTempFilePath(new Hash128()), value); - - throw new InvalidOperationException($"Cache collision found on {GenCacheTempFilePath(keyHash)}"); - } - - // Queue file change operations - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.SaveTempEntry, - Data = new CacheFileSaveEntryTaskData - { - Key = keyHash, - Value = value - } - }); - - // Save the manifest changes - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.SaveManifest, - }); - } - - /// <summary> - /// Replace a value at the given hash in the cache. - /// </summary> - /// <param name="keyHash">The hash to use for the value in the cache</param> - /// <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 - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.SaveTempEntry, - Data = new CacheFileSaveEntryTaskData - { - Key = keyHash, - Value = value - } - }); - } - - /// <summary> - /// Removes a value at the given hash from the cache. - /// </summary> - /// <param name="keyHash">The hash of the value in the cache</param> - public void RemoveValue(ref Hash128 keyHash) - { - if (IsReadOnly) - { - Logger.Warning?.Print(LogClass.Gpu, $"Trying to remove {keyHash} on a read-only cache, ignoring."); - - return; - } - - // Only queue file change operations - _fileWriterWorkerQueue.Add(new CacheFileOperationTask - { - Type = CacheFileOperation.RemoveManifestEntryAndTempFile, - Data = keyHash - }); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - // Make sure all operations on _fileWriterWorkerQueue are done. - Synchronize(); - - _fileWriterWorkerQueue.Dispose(); - EnsureArchiveUpToDate(); - } - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs deleted file mode 100644 index d16afb65..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheHelper.cs +++ /dev/null @@ -1,273 +0,0 @@ -using ICSharpCode.SharpZipLib.Zip; -using Ryujinx.Common; -using Ryujinx.Common.Configuration; -using Ryujinx.Common.Logging; -using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; -using Ryujinx.Graphics.Shader; -using System; -using System.Collections.Generic; -using System.IO; -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> - /// 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(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(ZipFile archive, Hash128 entry) - { - if (archive != null) - { - ZipEntry archiveEntry = archive.GetEntry($"{entry}"); - - if (archiveEntry != null) - { - try - { - byte[] result = new byte[archiveEntry.Size]; - - using (Stream archiveStream = archive.GetInputStream(archiveEntry)) - { - 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> - /// 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 TransformFeedbackDescriptorOld[] ReadTransformFeedbackInformation(ref ReadOnlySpan<byte> data, GuestShaderCacheHeader header) - { - if (header.TransformFeedbackCount != 0) - { - TransformFeedbackDescriptorOld[] result = new TransformFeedbackDescriptorOld[header.TransformFeedbackCount]; - - for (int i = 0; i < result.Length; i++) - { - GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read<GuestShaderCacheTransformFeedbackHeader>(data); - - result[i] = new TransformFeedbackDescriptorOld(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> - /// 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, ZipFile archive, HashSet<Hash128> entries) - { - List<string> filesToDelete = new List<string>(); - - archive.BeginUpdate(); - - foreach (Hash128 hash in entries) - { - string cacheTempFilePath = GenCacheTempFilePath(baseCacheDirectory, hash); - - if (File.Exists(cacheTempFilePath)) - { - string cacheHash = $"{hash}"; - - ZipEntry entry = archive.GetEntry(cacheHash); - - if (entry != null) - { - archive.Delete(entry); - } - - // We enforce deflate compression here to avoid possible incompatibilities on older version of Ryujinx that use System.IO.Compression. - archive.Add(new StaticDiskDataSource(cacheTempFilePath), cacheHash, CompressionMethod.Deflated); - filesToDelete.Add(cacheTempFilePath); - } - } - - archive.CommitUpdate(); - - foreach (string filePath in filesToDelete) - { - File.Delete(filePath); - } - } - - 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 deleted file mode 100644 index e67221e7..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs +++ /dev/null @@ -1,168 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache -{ - /// <summary> - /// Global Manager of the shader cache. - /// </summary> - class CacheManager : IDisposable - { - private CacheGraphicsApi _graphicsApi; - private CacheHashType _hashType; - private string _shaderProvider; - - /// <summary> - /// Cache storing raw Maxwell shaders as programs. - /// </summary> - private CacheCollection _guestProgramCache; - - /// <summary> - /// Cache storing raw host programs. - /// </summary> - private CacheCollection _hostProgramCache; - - /// <summary> - /// Version of the guest cache shader (to increment when guest cache structure change). - /// </summary> - private const ulong GuestCacheVersion = 1759; - - public bool IsReadOnly => _guestProgramCache.IsReadOnly || _hostProgramCache.IsReadOnly; - - /// <summary> - /// Create a new cache manager instance - /// </summary> - /// <param name="graphicsApi">The graphics api in use</param> - /// <param name="hashType">The hash type in use for the cache</param> - /// <param name="shaderProvider">The name of the codegen provider</param> - /// <param name="titleId">The guest application title ID</param> - /// <param name="shaderCodeGenVersion">Version of the codegen</param> - public CacheManager(CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider, string titleId, ulong shaderCodeGenVersion) - { - _graphicsApi = graphicsApi; - _hashType = hashType; - _shaderProvider = shaderProvider; - - string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(titleId); - - _guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion); - _hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion); - } - - - /// <summary> - /// Entries to remove from the manifest. - /// </summary> - /// <param name="entries">Entries to remove from the manifest of all caches</param> - public void RemoveManifestEntries(HashSet<Hash128> entries) - { - _guestProgramCache.RemoveManifestEntriesAsync(entries); - _hostProgramCache.RemoveManifestEntriesAsync(entries); - } - - /// <summary> - /// Queue a task to flush temporary files to the archives. - /// </summary> - public void FlushToArchive() - { - _guestProgramCache.FlushToArchiveAsync(); - _hostProgramCache.FlushToArchiveAsync(); - } - - /// <summary> - /// Wait for all tasks before this given point to be done. - /// </summary> - public void Synchronize() - { - _guestProgramCache.Synchronize(); - _hostProgramCache.Synchronize(); - } - - /// <summary> - /// Save a shader program not present in the program cache. - /// </summary> - /// <param name="programCodeHash">Target program code hash</param> - /// <param name="guestProgram">Guest program raw data</param> - /// <param name="hostProgram">Host program raw data</param> - public void SaveProgram(ref Hash128 programCodeHash, byte[] guestProgram, byte[] hostProgram) - { - _guestProgramCache.AddValue(ref programCodeHash, guestProgram); - _hostProgramCache.AddValue(ref programCodeHash, hostProgram); - } - - /// <summary> - /// Add a host shader program not present in the program cache. - /// </summary> - /// <param name="programCodeHash">Target program code hash</param> - /// <param name="data">Host program raw data</param> - public void AddHostProgram(ref Hash128 programCodeHash, byte[] data) - { - _hostProgramCache.AddValue(ref programCodeHash, data); - } - - /// <summary> - /// Replace a host shader program present in the program cache. - /// </summary> - /// <param name="programCodeHash">Target program code hash</param> - /// <param name="data">Host program raw data</param> - public void ReplaceHostProgram(ref Hash128 programCodeHash, byte[] data) - { - _hostProgramCache.ReplaceValue(ref programCodeHash, data); - } - - /// <summary> - /// Removes a shader program present in the program cache. - /// </summary> - /// <param name="programCodeHash">Target program code hash</param> - public void RemoveProgram(ref Hash128 programCodeHash) - { - _guestProgramCache.RemoveValue(ref programCodeHash); - _hostProgramCache.RemoveValue(ref programCodeHash); - } - - /// <summary> - /// Get all guest program hashes. - /// </summary> - /// <returns>All guest program hashes</returns> - public ReadOnlySpan<Hash128> GetGuestProgramList() - { - return _guestProgramCache.HashTable; - } - - /// <summary> - /// Get a host program by hash. - /// </summary> - /// <param name="hash">The given hash</param> - /// <returns>The host program if present or null</returns> - public byte[] GetHostProgramByHash(ref Hash128 hash) - { - return _hostProgramCache.GetValueRaw(ref hash); - } - - /// <summary> - /// Get a guest program by hash. - /// </summary> - /// <param name="hash">The given hash</param> - /// <returns>The guest program if present or null</returns> - public byte[] GetGuestProgramByHash(ref Hash128 hash) - { - return _guestProgramCache.GetValueRaw(ref hash); - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _guestProgramCache.Dispose(); - _hostProgramCache.Dispose(); - } - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs deleted file mode 100644 index 9f8b5c39..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs +++ /dev/null @@ -1,38 +0,0 @@ -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Graphics API type accepted by the shader cache. - /// </summary> - enum CacheGraphicsApi : byte - { - /// <summary> - /// OpenGL Core - /// </summary> - OpenGL, - - /// <summary> - /// OpenGL ES - /// </summary> - OpenGLES, - - /// <summary> - /// Vulkan - /// </summary> - Vulkan, - - /// <summary> - /// DirectX - /// </summary> - DirectX, - - /// <summary> - /// Metal - /// </summary> - Metal, - - /// <summary> - /// Guest, used to cache games raw shader programs. - /// </summary> - Guest - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs deleted file mode 100644 index e4ebe416..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Hash algorithm accepted by the shader cache. - /// </summary> - enum CacheHashType : byte - { - /// <summary> - /// xxHash128 - /// </summary> - XxHash128 - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs deleted file mode 100644 index 0601451d..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Header of the shader cache manifest. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] - struct CacheManifestHeader - { - /// <summary> - /// The version of the cache. - /// </summary> - public ulong Version; - - /// <summary> - /// The graphics api used for this cache. - /// </summary> - public CacheGraphicsApi GraphicsApi; - - /// <summary> - /// The hash type used for this cache. - /// </summary> - public CacheHashType HashType; - - /// <summary> - /// CRC-16 checksum over the data in the file. - /// </summary> - public ushort TableChecksum; - - /// <summary> - /// Construct a new cache manifest header. - /// </summary> - /// <param name="version">The version of the cache</param> - /// <param name="graphicsApi">The graphics api used for this cache</param> - /// <param name="hashType">The hash type used for this cache</param> - public CacheManifestHeader(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType) - { - Version = version; - GraphicsApi = graphicsApi; - HashType = hashType; - TableChecksum = 0; - } - - /// <summary> - /// Update the checksum in the header. - /// </summary> - /// <param name="data">The data to perform the checksum on</param> - public void UpdateChecksum(ReadOnlySpan<byte> data) - { - TableChecksum = CalculateCrc16(data); - } - - /// <summary> - /// Calculate a CRC-16 over data. - /// </summary> - /// <param name="data">The data to perform the CRC-16 on</param> - /// <returns>A CRC-16 over data</returns> - private static ushort CalculateCrc16(ReadOnlySpan<byte> data) - { - int crc = 0; - - const ushort poly = 0x1021; - - for (int i = 0; i < data.Length; i++) - { - crc ^= data[i] << 8; - - for (int j = 0; j < 8; j++) - { - crc <<= 1; - - if ((crc & 0x10000) != 0) - { - crc = (crc ^ poly) & 0xFFFF; - } - } - } - - return (ushort)crc; - } - - /// <summary> - /// Check the validity of the header. - /// </summary> - /// <param name="graphicsApi">The target graphics api in use</param> - /// <param name="hashType">The target hash type in use</param> - /// <param name="data">The data after this header</param> - /// <returns>True if the header is valid</returns> - /// <remarks>This doesn't check that versions match</remarks> - public bool IsValid(CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan<byte> data) - { - return GraphicsApi == graphicsApi && HashType == hashType && TableChecksum == CalculateCrc16(data); - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs deleted file mode 100644 index 2e044750..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs +++ /dev/null @@ -1,67 +0,0 @@ -using Ryujinx.Graphics.Shader; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Header of a cached guest gpu accessor. - /// </summary> - [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)] - struct GuestGpuAccessorHeader - { - /// <summary> - /// The count of texture descriptors. - /// </summary> - public int TextureDescriptorCount; - - /// <summary> - /// Local Size X for compute shaders. - /// </summary> - public int ComputeLocalSizeX; - - /// <summary> - /// Local Size Y for compute shaders. - /// </summary> - public int ComputeLocalSizeY; - - /// <summary> - /// Local Size Z for compute shaders. - /// </summary> - public int ComputeLocalSizeZ; - - /// <summary> - /// Local Memory size in bytes for compute shaders. - /// </summary> - public int ComputeLocalMemorySize; - - /// <summary> - /// Shared Memory size in bytes for compute shaders. - /// </summary> - public int ComputeSharedMemorySize; - - /// <summary> - /// Unused/reserved. - /// </summary> - public int Reserved1; - - /// <summary> - /// Current primitive topology for geometry shaders. - /// </summary> - public InputTopology PrimitiveTopology; - - /// <summary> - /// Tessellation parameters (packed to fit on a byte). - /// </summary> - public byte TessellationModePacked; - - /// <summary> - /// Unused/reserved. - /// </summary> - public byte Reserved2; - - /// <summary> - /// GPU boolean state that can influence shader compilation. - /// </summary> - public GuestGpuStateFlags StateFlags; - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs deleted file mode 100644 index 4b1fbb06..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuStateFlags.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - [Flags] - enum GuestGpuStateFlags : byte - { - EarlyZForce = 1 << 0 - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs deleted file mode 100644 index 373fa6c6..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Represent a cached shader entry in a guest shader program. - /// </summary> - class GuestShaderCacheEntry - { - /// <summary> - /// The header of the cached shader entry. - /// </summary> - public GuestShaderCacheEntryHeader Header { get; } - - /// <summary> - /// The code of this shader. - /// </summary> - /// <remarks>If a Vertex A is present, this also contains the code 2 section.</remarks> - public byte[] Code { get; } - - /// <summary> - /// The textures descriptors used for this shader. - /// </summary> - public Dictionary<int, GuestTextureDescriptor> TextureDescriptors { get; } - - /// <summary> - /// Create a new instance of <see cref="GuestShaderCacheEntry"/>. - /// </summary> - /// <param name="header">The header of the cached shader entry</param> - /// <param name="code">The code of this shader</param> - public GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code) - { - Header = header; - Code = code; - TextureDescriptors = new Dictionary<int, GuestTextureDescriptor>(); - } - - /// <summary> - /// Parse a raw cached user shader program into an array of shader cache entry. - /// </summary> - /// <param name="data">The raw cached user shader program</param> - /// <param name="fileHeader">The user shader program header</param> - /// <returns>An array of shader cache entry</returns> - public static GuestShaderCacheEntry[] Parse(ref ReadOnlySpan<byte> data, out GuestShaderCacheHeader fileHeader) - { - fileHeader = MemoryMarshal.Read<GuestShaderCacheHeader>(data); - - data = data.Slice(Unsafe.SizeOf<GuestShaderCacheHeader>()); - - ReadOnlySpan<GuestShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, GuestShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>())); - - data = data.Slice(fileHeader.Count * Unsafe.SizeOf<GuestShaderCacheEntryHeader>()); - - GuestShaderCacheEntry[] result = new GuestShaderCacheEntry[fileHeader.Count]; - - for (int i = 0; i < result.Length; i++) - { - GuestShaderCacheEntryHeader header = entryHeaders[i]; - - // Ignore empty entries - if (header.Size == 0 && header.SizeA == 0) - { - continue; - } - - byte[] code = data.Slice(0, header.Size + header.SizeA).ToArray(); - - data = data.Slice(header.Size + header.SizeA); - - result[i] = new GuestShaderCacheEntry(header, code); - - ReadOnlySpan<GuestTextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, GuestTextureDescriptor>(data.Slice(0, header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>())); - - foreach (GuestTextureDescriptor textureDescriptor in textureDescriptors) - { - result[i].TextureDescriptors.Add((int)textureDescriptor.Handle, textureDescriptor); - } - - data = data.Slice(header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf<GuestTextureDescriptor>()); - } - - return result; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs deleted file mode 100644 index 9b22cac5..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Ryujinx.Graphics.Shader; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// The header of a guest shader entry in a guest shader program. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x30)] - struct GuestShaderCacheEntryHeader - { - /// <summary> - /// The stage of this shader. - /// </summary> - public ShaderStage Stage; - - /// <summary> - /// Unused/reserved. - /// </summary> - public byte Reserved1; - - /// <summary> - /// Unused/reserved. - /// </summary> - public byte Reserved2; - - /// <summary> - /// Unused/reserved. - /// </summary> - public byte Reserved3; - - /// <summary> - /// The size of the code section. - /// </summary> - public int Size; - - /// <summary> - /// The size of the code2 section if present. (Vertex A) - /// </summary> - public int SizeA; - - /// <summary> - /// Constant buffer 1 data size. - /// </summary> - public int Cb1DataSize; - - /// <summary> - /// The header of the cached gpu accessor. - /// </summary> - public GuestGpuAccessorHeader GpuAccessorHeader; - - /// <summary> - /// Create a new guest shader entry header. - /// </summary> - /// <param name="stage">The stage of this shader</param> - /// <param name="size">The size of the code section</param> - /// <param name="sizeA">The size of the code2 section if present (Vertex A)</param> - /// <param name="cb1DataSize">Constant buffer 1 data size</param> - /// <param name="gpuAccessorHeader">The header of the cached gpu accessor</param> - public GuestShaderCacheEntryHeader(ShaderStage stage, int size, int sizeA, int cb1DataSize, GuestGpuAccessorHeader gpuAccessorHeader) : this() - { - Stage = stage; - Size = size; - SizeA = sizeA; - Cb1DataSize = cb1DataSize; - GpuAccessorHeader = gpuAccessorHeader; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs deleted file mode 100644 index 700be47d..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// The header of a shader program in the guest cache. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)] - struct GuestShaderCacheHeader - { - /// <summary> - /// The count of shaders defining this program. - /// </summary> - public byte Count; - - /// <summary> - /// The count of transform feedback data used in this program. - /// </summary> - public byte TransformFeedbackCount; - - /// <summary> - /// Unused/reserved. - /// </summary> - public ushort Reserved1; - - /// <summary> - /// Unused/reserved. - /// </summary> - public ulong Reserved2; - - /// <summary> - /// Create a new guest shader cache header. - /// </summary> - /// <param name="count">The count of shaders defining this program</param> - /// <param name="transformFeedbackCount">The count of transform feedback data used in this program</param> - public GuestShaderCacheHeader(byte count, byte transformFeedbackCount) : this() - { - Count = count; - TransformFeedbackCount = transformFeedbackCount; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs deleted file mode 100644 index 18cfdf55..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Header for transform feedback. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)] - struct GuestShaderCacheTransformFeedbackHeader - { - /// <summary> - /// The buffer index of the transform feedback. - /// </summary> - public int BufferIndex; - - /// <summary> - /// The stride of the transform feedback. - /// </summary> - public int Stride; - - /// <summary> - /// The length of the varying location buffer of the transform feedback. - /// </summary> - public int VaryingLocationsLength; - - /// <summary> - /// Reserved/unused. - /// </summary> - public int Reserved1; - - public GuestShaderCacheTransformFeedbackHeader(int bufferIndex, int stride, int varyingLocationsLength) : this() - { - BufferIndex = bufferIndex; - Stride = stride; - VaryingLocationsLength = varyingLocationsLength; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs deleted file mode 100644 index 9491496d..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Ryujinx.Graphics.Gpu.Image; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Contains part of TextureDescriptor from <see cref="Image"/> used for shader codegen. - /// </summary> - [StructLayout(LayoutKind.Sequential, Size = 0xC, Pack = 1)] - struct GuestTextureDescriptor : ITextureDescriptor - { - public uint Handle; - public uint Format; - public TextureTarget Target; - [MarshalAs(UnmanagedType.I1)] - public bool IsSrgb; - [MarshalAs(UnmanagedType.I1)] - public bool IsTextureCoordNormalized; - public byte Reserved; - - public uint UnpackFormat() - { - return Format; - } - - public bool UnpackSrgb() - { - return IsSrgb; - } - - public bool UnpackTextureCoordNormalized() - { - return IsTextureCoordNormalized; - } - - public TextureTarget UnpackTextureTarget() - { - return Target; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs deleted file mode 100644 index fe79acb3..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs +++ /dev/null @@ -1,222 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Graphics.Shader; -using System; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Host shader entry used for binding information. - /// </summary> - class HostShaderCacheEntry - { - /// <summary> - /// The header of the cached shader entry. - /// </summary> - public HostShaderCacheEntryHeader Header { get; } - - /// <summary> - /// Cached constant buffers. - /// </summary> - public BufferDescriptor[] CBuffers { get; } - - /// <summary> - /// Cached storage buffers. - /// </summary> - public BufferDescriptor[] SBuffers { get; } - - /// <summary> - /// Cached texture descriptors. - /// </summary> - public TextureDescriptor[] Textures { get; } - - /// <summary> - /// Cached image descriptors. - /// </summary> - public TextureDescriptor[] Images { get; } - - /// <summary> - /// Create a new instance of <see cref="HostShaderCacheEntry"/>. - /// </summary> - /// <param name="header">The header of the cached shader entry</param> - /// <param name="cBuffers">Cached constant buffers</param> - /// <param name="sBuffers">Cached storage buffers</param> - /// <param name="textures">Cached texture descriptors</param> - /// <param name="images">Cached image descriptors</param> - private HostShaderCacheEntry( - HostShaderCacheEntryHeader header, - BufferDescriptor[] cBuffers, - BufferDescriptor[] sBuffers, - TextureDescriptor[] textures, - TextureDescriptor[] images) - { - Header = header; - CBuffers = cBuffers; - SBuffers = sBuffers; - Textures = textures; - Images = images; - } - - private HostShaderCacheEntry() - { - Header = new HostShaderCacheEntryHeader(); - CBuffers = new BufferDescriptor[0]; - SBuffers = new BufferDescriptor[0]; - Textures = new TextureDescriptor[0]; - Images = new TextureDescriptor[0]; - } - - private HostShaderCacheEntry(ShaderProgramInfo programInfo) - { - Header = new HostShaderCacheEntryHeader(programInfo.CBuffers.Count, - programInfo.SBuffers.Count, - programInfo.Textures.Count, - programInfo.Images.Count, - programInfo.UsesInstanceId, - programInfo.UsesRtLayer, - programInfo.ClipDistancesWritten, - programInfo.FragmentOutputMap); - CBuffers = programInfo.CBuffers.ToArray(); - SBuffers = programInfo.SBuffers.ToArray(); - Textures = programInfo.Textures.ToArray(); - Images = programInfo.Images.ToArray(); - } - - /// <summary> - /// Convert the host shader entry to a <see cref="ShaderProgramInfo"/>. - /// </summary> - /// <returns>A new <see cref="ShaderProgramInfo"/> from this instance</returns> - internal ShaderProgramInfo ToShaderProgramInfo() - { - return new ShaderProgramInfo( - CBuffers, - SBuffers, - Textures, - Images, - default, - Header.UseFlags.HasFlag(UseFlags.InstanceId), - Header.UseFlags.HasFlag(UseFlags.RtLayer), - Header.ClipDistancesWritten, - Header.FragmentOutputMap); - } - - /// <summary> - /// Parse a raw cached user shader program into an array of shader cache entry. - /// </summary> - /// <param name="data">The raw cached host shader</param> - /// <param name="programCode">The host shader program</param> - /// <returns>An array of shader cache entry</returns> - internal static HostShaderCacheEntry[] Parse(ReadOnlySpan<byte> data, out ReadOnlySpan<byte> programCode) - { - HostShaderCacheHeader fileHeader = MemoryMarshal.Read<HostShaderCacheHeader>(data); - - data = data.Slice(Unsafe.SizeOf<HostShaderCacheHeader>()); - - ReadOnlySpan<HostShaderCacheEntryHeader> entryHeaders = MemoryMarshal.Cast<byte, HostShaderCacheEntryHeader>(data.Slice(0, fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>())); - - data = data.Slice(fileHeader.Count * Unsafe.SizeOf<HostShaderCacheEntryHeader>()); - - HostShaderCacheEntry[] result = new HostShaderCacheEntry[fileHeader.Count]; - - for (int i = 0; i < result.Length; i++) - { - HostShaderCacheEntryHeader header = entryHeaders[i]; - - if (!header.InUse) - { - continue; - } - - int cBufferDescriptorsSize = header.CBuffersCount * Unsafe.SizeOf<BufferDescriptor>(); - int sBufferDescriptorsSize = header.SBuffersCount * Unsafe.SizeOf<BufferDescriptor>(); - int textureDescriptorsSize = header.TexturesCount * Unsafe.SizeOf<TextureDescriptor>(); - int imageDescriptorsSize = header.ImagesCount * Unsafe.SizeOf<TextureDescriptor>(); - - ReadOnlySpan<BufferDescriptor> cBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, cBufferDescriptorsSize)); - data = data.Slice(cBufferDescriptorsSize); - - ReadOnlySpan<BufferDescriptor> sBuffers = MemoryMarshal.Cast<byte, BufferDescriptor>(data.Slice(0, sBufferDescriptorsSize)); - data = data.Slice(sBufferDescriptorsSize); - - ReadOnlySpan<TextureDescriptor> textureDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, textureDescriptorsSize)); - data = data.Slice(textureDescriptorsSize); - - ReadOnlySpan<TextureDescriptor> imageDescriptors = MemoryMarshal.Cast<byte, TextureDescriptor>(data.Slice(0, imageDescriptorsSize)); - data = data.Slice(imageDescriptorsSize); - - result[i] = new HostShaderCacheEntry(header, cBuffers.ToArray(), sBuffers.ToArray(), textureDescriptors.ToArray(), imageDescriptors.ToArray()); - } - - programCode = data.Slice(0, fileHeader.CodeSize); - - return result; - } - - /// <summary> - /// Create a new host shader cache file. - /// </summary> - /// <param name="programCode">The host shader program</param> - /// <param name="codeHolders">The shaders code holder</param> - /// <returns>Raw data of a new host shader cache file</returns> - internal static byte[] Create(ReadOnlySpan<byte> programCode, CachedShaderStage[] codeHolders) - { - HostShaderCacheHeader header = new HostShaderCacheHeader((byte)codeHolders.Length, programCode.Length); - - HostShaderCacheEntry[] entries = new HostShaderCacheEntry[codeHolders.Length]; - - for (int i = 0; i < codeHolders.Length; i++) - { - if (codeHolders[i] == null) - { - entries[i] = new HostShaderCacheEntry(); - } - else - { - entries[i] = new HostShaderCacheEntry(codeHolders[i].Info); - } - } - - using (MemoryStream stream = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(stream); - - writer.WriteStruct(header); - - foreach (HostShaderCacheEntry entry in entries) - { - writer.WriteStruct(entry.Header); - } - - foreach (HostShaderCacheEntry entry in entries) - { - foreach (BufferDescriptor cBuffer in entry.CBuffers) - { - writer.WriteStruct(cBuffer); - } - - foreach (BufferDescriptor sBuffer in entry.SBuffers) - { - writer.WriteStruct(sBuffer); - } - - foreach (TextureDescriptor texture in entry.Textures) - { - writer.WriteStruct(texture); - } - - foreach (TextureDescriptor image in entry.Images) - { - writer.WriteStruct(image); - } - } - - writer.Write(programCode); - - return stream.ToArray(); - } - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs deleted file mode 100644 index c3c0de22..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs +++ /dev/null @@ -1,114 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// Flags indicating if the shader accesses certain built-ins, such as the instance ID. - /// </summary> - enum UseFlags : byte - { - /// <summary> - /// None of the built-ins are used. - /// </summary> - None = 0, - - /// <summary> - /// Indicates whenever the vertex shader reads the gl_InstanceID built-in. - /// </summary> - InstanceId = 1 << 0, - - /// <summary> - /// Indicates whenever any of the VTG stages writes to the gl_Layer built-in. - /// </summary> - RtLayer = 1 << 1 - } - - /// <summary> - /// Host shader entry header used for binding information. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x18)] - struct HostShaderCacheEntryHeader - { - /// <summary> - /// Count of constant buffer descriptors. - /// </summary> - public int CBuffersCount; - - /// <summary> - /// Count of storage buffer descriptors. - /// </summary> - public int SBuffersCount; - - /// <summary> - /// Count of texture descriptors. - /// </summary> - public int TexturesCount; - - /// <summary> - /// Count of image descriptors. - /// </summary> - public int ImagesCount; - - /// <summary> - /// Flags indicating if the shader accesses certain built-ins, such as the instance ID. - /// </summary> - public UseFlags UseFlags; - - /// <summary> - /// Set to true if this entry is in use. - /// </summary> - [MarshalAs(UnmanagedType.I1)] - public bool InUse; - - /// <summary> - /// Mask of clip distances that are written to on the shader. - /// </summary> - public byte ClipDistancesWritten; - - /// <summary> - /// Reserved / unused. - /// </summary> - public byte Reserved; - - /// <summary> - /// Mask of components written by the fragment shader stage. - /// </summary> - public int FragmentOutputMap; - - /// <summary> - /// Create a new host shader cache entry header. - /// </summary> - /// <param name="cBuffersCount">Count of constant buffer descriptors</param> - /// <param name="sBuffersCount">Count of storage buffer descriptors</param> - /// <param name="texturesCount">Count of texture descriptors</param> - /// <param name="imagesCount">Count of image descriptors</param> - /// <param name="usesInstanceId">Set to true if the shader uses instance id</param> - /// <param name="clipDistancesWritten">Mask of clip distances that are written to on the shader</param> - /// <param name="fragmentOutputMap">Mask of components written by the fragment shader stage</param> - public HostShaderCacheEntryHeader( - int cBuffersCount, - int sBuffersCount, - int texturesCount, - int imagesCount, - bool usesInstanceId, - bool usesRtLayer, - byte clipDistancesWritten, - int fragmentOutputMap) : this() - { - CBuffersCount = cBuffersCount; - SBuffersCount = sBuffersCount; - TexturesCount = texturesCount; - ImagesCount = imagesCount; - ClipDistancesWritten = clipDistancesWritten; - FragmentOutputMap = fragmentOutputMap; - InUse = true; - - UseFlags = usesInstanceId ? UseFlags.InstanceId : UseFlags.None; - - if (usesRtLayer) - { - UseFlags |= UseFlags.RtLayer; - } - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs deleted file mode 100644 index 27f216cc..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition -{ - /// <summary> - /// The header of a shader program in the guest cache. - /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)] - struct HostShaderCacheHeader - { - /// <summary> - /// The count of shaders defining this program. - /// </summary> - public byte Count; - - /// <summary> - /// Unused/reserved. - /// </summary> - public byte Reserved1; - - /// <summary> - /// Unused/reserved. - /// </summary> - public ushort Reserved2; - - /// <summary> - /// Size of the shader binary. - /// </summary> - public int CodeSize; - - /// <summary> - /// Create a new host shader cache header. - /// </summary> - /// <param name="count">The count of shaders defining this program</param> - /// <param name="codeSize">The size of the shader binary</param> - public HostShaderCacheHeader(byte count, int codeSize) : this() - { - Count = count; - CodeSize = codeSize; - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs deleted file mode 100644 index 885bcd09..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/Migration.cs +++ /dev/null @@ -1,258 +0,0 @@ -using Ryujinx.Common; -using Ryujinx.Common.Logging; -using Ryujinx.Common.Memory; -using Ryujinx.Graphics.GAL; -using Ryujinx.Graphics.Gpu.Engine.Threed; -using Ryujinx.Graphics.Gpu.Shader.Cache.Definition; -using Ryujinx.Graphics.Gpu.Shader.DiskCache; -using Ryujinx.Graphics.Shader; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache -{ - /// <summary> - /// Class handling shader cache migrations. - /// </summary> - static class Migration - { - // Last codegen version before the migration to the new cache. - private const ulong ShaderCodeGenVersion = 3054; - - /// <summary> - /// Migrates from the old cache format to the new one. - /// </summary> - /// <param name="context">GPU context</param> - /// <param name="hostStorage">Disk cache host storage (used to create the new shader files)</param> - /// <returns>Number of migrated shaders</returns> - public static int MigrateFromLegacyCache(GpuContext context, DiskCacheHostStorage hostStorage) - { - string baseCacheDirectory = CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId); - string cacheDirectory = CacheHelper.GenerateCachePath(baseCacheDirectory, CacheGraphicsApi.Guest, "", "program"); - - // If the directory does not exist, we have no old cache. - // Exist early as the CacheManager constructor will create the directories. - if (!Directory.Exists(cacheDirectory)) - { - return 0; - } - - if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null) - { - CacheManager cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion); - - 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(); - - for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++) - { - Hash128 key = guestProgramList[programIndex]; - - byte[] guestProgram = cacheManager.GetGuestProgramByHash(ref key); - - if (guestProgram == null) - { - Logger.Error?.Print(LogClass.Gpu, $"Ignoring orphan shader hash {key} in cache (is the cache incomplete?)"); - - continue; - } - - ReadOnlySpan<byte> guestProgramReadOnlySpan = guestProgram; - - ReadOnlySpan<GuestShaderCacheEntry> cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader); - - if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute) - { - Debug.Assert(cachedShaderEntries.Length == 1); - - GuestShaderCacheEntry entry = cachedShaderEntries[0]; - - byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); - - Span<byte> codeSpan = entry.Code; - byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray(); - - ShaderProgramInfo info = new ShaderProgramInfo( - Array.Empty<BufferDescriptor>(), - Array.Empty<BufferDescriptor>(), - Array.Empty<TextureDescriptor>(), - Array.Empty<TextureDescriptor>(), - ShaderStage.Compute, - false, - false, - 0, - 0); - - GpuChannelComputeState computeState = new GpuChannelComputeState( - entry.Header.GpuAccessorHeader.ComputeLocalSizeX, - entry.Header.GpuAccessorHeader.ComputeLocalSizeY, - entry.Header.GpuAccessorHeader.ComputeLocalSizeZ, - entry.Header.GpuAccessorHeader.ComputeLocalMemorySize, - entry.Header.GpuAccessorHeader.ComputeSharedMemorySize); - - ShaderSpecializationState specState = new ShaderSpecializationState(computeState); - - foreach (var td in entry.TextureDescriptors) - { - var handle = td.Key; - var data = td.Value; - - specState.RegisterTexture( - 0, - handle, - -1, - data.UnpackFormat(), - data.UnpackSrgb(), - data.UnpackTextureTarget(), - data.UnpackTextureCoordNormalized()); - } - - CachedShaderStage shader = new CachedShaderStage(info, code, cb1Data); - CachedShaderProgram program = new CachedShaderProgram(null, specState, shader); - - hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty); - } - else - { - Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages); - - CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1]; - List<ShaderProgram> shaderPrograms = new List<ShaderProgram>(); - - TransformFeedbackDescriptorOld[] tfd = CacheHelper.ReadTransformFeedbackInformation(ref guestProgramReadOnlySpan, fileHeader); - - GuestShaderCacheEntry[] entries = cachedShaderEntries.ToArray(); - - GuestGpuAccessorHeader accessorHeader = entries[0].Header.GpuAccessorHeader; - - TessMode tessMode = new TessMode(); - - int tessPatchType = accessorHeader.TessellationModePacked & 3; - int tessSpacing = (accessorHeader.TessellationModePacked >> 2) & 3; - bool tessCw = (accessorHeader.TessellationModePacked & 0x10) != 0; - - tessMode.Packed = (uint)tessPatchType; - tessMode.Packed |= (uint)(tessSpacing << 4); - - if (tessCw) - { - tessMode.Packed |= 0x100; - } - - PrimitiveTopology topology = accessorHeader.PrimitiveTopology switch - { - InputTopology.Lines => PrimitiveTopology.Lines, - InputTopology.LinesAdjacency => PrimitiveTopology.LinesAdjacency, - InputTopology.Triangles => PrimitiveTopology.Triangles, - InputTopology.TrianglesAdjacency => PrimitiveTopology.TrianglesAdjacency, - _ => PrimitiveTopology.Points - }; - - GpuChannelGraphicsState graphicsState = new GpuChannelGraphicsState( - accessorHeader.StateFlags.HasFlag(GuestGpuStateFlags.EarlyZForce), - topology, - tessMode, - false, - false, - false); - - TransformFeedbackDescriptor[] tfdNew = null; - - if (tfd != null) - { - tfdNew = new TransformFeedbackDescriptor[tfd.Length]; - - for (int tfIndex = 0; tfIndex < tfd.Length; tfIndex++) - { - Array32<uint> varyingLocations = new Array32<uint>(); - Span<byte> varyingLocationsSpan = MemoryMarshal.Cast<uint, byte>(varyingLocations.ToSpan()); - tfd[tfIndex].VaryingLocations.CopyTo(varyingLocationsSpan.Slice(0, tfd[tfIndex].VaryingLocations.Length)); - - tfdNew[tfIndex] = new TransformFeedbackDescriptor( - tfd[tfIndex].BufferIndex, - tfd[tfIndex].Stride, - tfd[tfIndex].VaryingLocations.Length, - ref varyingLocations); - } - } - - ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, tfdNew); - - for (int i = 0; i < entries.Length; i++) - { - GuestShaderCacheEntry entry = entries[i]; - - if (entry == null) - { - continue; - } - - ShaderProgramInfo info = new ShaderProgramInfo( - Array.Empty<BufferDescriptor>(), - Array.Empty<BufferDescriptor>(), - Array.Empty<TextureDescriptor>(), - Array.Empty<TextureDescriptor>(), - (ShaderStage)(i + 1), - false, - false, - 0, - 0); - - // NOTE: Vertex B comes first in the shader cache. - byte[] code = entry.Code.AsSpan(0, entry.Header.Size - entry.Header.Cb1DataSize).ToArray(); - byte[] code2 = entry.Header.SizeA != 0 ? entry.Code.AsSpan(entry.Header.Size, entry.Header.SizeA).ToArray() : null; - - Span<byte> codeSpan = entry.Code; - byte[] cb1Data = codeSpan.Slice(codeSpan.Length - entry.Header.Cb1DataSize).ToArray(); - - shaders[i + 1] = new CachedShaderStage(info, code, cb1Data); - - if (code2 != null) - { - shaders[0] = new CachedShaderStage(null, code2, cb1Data); - } - - foreach (var td in entry.TextureDescriptors) - { - var handle = td.Key; - var data = td.Value; - - specState.RegisterTexture( - i, - handle, - -1, - data.UnpackFormat(), - data.UnpackSrgb(), - data.UnpackTextureTarget(), - data.UnpackTextureCoordNormalized()); - } - } - - CachedShaderProgram program = new CachedShaderProgram(null, specState, shaders); - - hostStorage.AddShader(context, program, ReadOnlySpan<byte>.Empty); - } - } - - return guestProgramList.Length; - } - - return 0; - } - } -}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs deleted file mode 100644 index 5e9c6711..00000000 --- a/Ryujinx.Graphics.Gpu/Shader/Cache/TransformFeedbackDescriptorOld.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gpu.Shader.Cache -{ - struct TransformFeedbackDescriptorOld - { - public int BufferIndex { get; } - public int Stride { get; } - - public byte[] VaryingLocations { get; } - - public TransformFeedbackDescriptorOld(int bufferIndex, int stride, byte[] varyingLocations) - { - BufferIndex = bufferIndex; - Stride = stride; - VaryingLocations = varyingLocations ?? throw new ArgumentNullException(nameof(varyingLocations)); - } - } -} diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs index 5c5e41c6..98655ed6 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/BackgroundDiskCacheWriter.cs @@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { _context = context; _hostStorage = hostStorage; - _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "Gpu.BackgroundDiskCacheWriter"); + _fileWriterWorkerQueue = new AsyncWorkQueue<CacheFileOperationTask>(ProcessTask, "GPU.BackgroundDiskCacheWriter"); } /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs index e5476426..68ff4f2a 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGpuAccessor.cs @@ -1,6 +1,8 @@ using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.Runtime.InteropServices; @@ -16,7 +18,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private readonly ShaderSpecializationState _oldSpecState; private readonly ShaderSpecializationState _newSpecState; private readonly int _stageIndex; - private ResourceCounts _resourceCounts; + private readonly bool _isVulkan; + private readonly ResourceCounts _resourceCounts; /// <summary> /// Creates a new instance of the cached GPU state accessor for shader translation. @@ -34,13 +37,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache ShaderSpecializationState oldSpecState, ShaderSpecializationState newSpecState, ResourceCounts counts, - int stageIndex) : base(context) + int stageIndex) : base(context, counts, stageIndex) { _data = data; _cb1Data = cb1Data; _oldSpecState = oldSpecState; _newSpecState = newSpecState; _stageIndex = stageIndex; + _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _resourceCounts = counts; } @@ -74,27 +78,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> - public int QueryBindingConstantBuffer(int index) + public AlphaTestOp QueryAlphaTestCompare() { - return _resourceCounts.UniformBuffersCount++; - } + if (!_isVulkan || !_oldSpecState.GraphicsState.AlphaTestEnable) + { + return AlphaTestOp.Always; + } - /// <inheritdoc/> - public int QueryBindingStorageBuffer(int index) - { - return _resourceCounts.StorageBuffersCount++; + return _oldSpecState.GraphicsState.AlphaTestCompare switch + { + CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never, + CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less, + CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal, + CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual, + CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater, + CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual, + CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual, + _ => AlphaTestOp.Always + }; } /// <inheritdoc/> - public int QueryBindingTexture(int index) - { - return _resourceCounts.TexturesCount++; - } + public float QueryAlphaTestReference() => _oldSpecState.GraphicsState.AlphaTestReference; /// <inheritdoc/> - public int QueryBindingImage(int index) + public AttributeType QueryAttributeType(int location) { - return _resourceCounts.ImagesCount++; + return _oldSpecState.GraphicsState.AttributeTypes[location]; } /// <inheritdoc/> @@ -127,6 +137,18 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + public bool QueryProgramPointSize() + { + return _oldSpecState.GraphicsState.ProgramPointSizeEnable; + } + + /// <inheritdoc/> + public float QueryPointSize() + { + return _oldSpecState.GraphicsState.PointSize; + } + + /// <inheritdoc/> public bool QueryTessCw() { return _oldSpecState.GraphicsState.TessellationMode.UnpackCw(); @@ -167,6 +189,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <inheritdoc/> + public bool QueryTransformDepthMinusOneToOne() + { + return _oldSpecState.GraphicsState.DepthMode; + } + + /// <inheritdoc/> public bool QueryTransformFeedbackEnabled() { return _oldSpecState.TransformFeedbackDescriptors != null; diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs index 4e338094..01034b49 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheGuestStorage.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const uint TocMagic = (byte)'T' | ((byte)'O' << 8) | ((byte)'C' << 16) | ((byte)'G' << 24); private const ushort VersionMajor = 1; - private const ushort VersionMinor = 0; + private const ushort VersionMinor = 1; private const uint VersionPacked = ((uint)VersionMajor << 16) | VersionMinor; private const string TocFileName = "guest.toc"; @@ -193,8 +193,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <param name="tocFileStream">Guest TOC file stream</param> /// <param name="dataFileStream">Guest data file stream</param> /// <param name="index">Guest shader index</param> - /// <returns>Tuple with the guest code and constant buffer 1 data, respectively</returns> - public (byte[], byte[]) LoadShader(Stream tocFileStream, Stream dataFileStream, int index) + /// <returns>Guest code and constant buffer 1 data</returns> + public GuestCodeAndCbData LoadShader(Stream tocFileStream, Stream dataFileStream, int index) { if (_cache == null || index >= _cache.Length) { @@ -226,7 +226,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache _cache[index] = (guestCode, cb1Data); } - return (guestCode, cb1Data); + return new GuestCodeAndCbData(guestCode, cb1Data); } /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs index a47af942..b625835c 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/DiskCacheHostStorage.cs @@ -1,5 +1,6 @@ using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.IO; using System.Numerics; @@ -19,9 +20,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private const uint TexdMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'D' << 24); private const ushort FileFormatVersionMajor = 1; - private const ushort FileFormatVersionMinor = 1; + private const ushort FileFormatVersionMinor = 2; private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor; - private const uint CodeGenVersion = 3469; + private const uint CodeGenVersion = 13; private const string SharedTocFileName = "shared.toc"; private const string SharedDataFileName = "shared.data"; @@ -56,14 +57,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache public uint Padding; /// <summary> - /// Reserved space, to be used in the future. Write as zero. + /// Timestamp of when the file was first created. /// </summary> - public ulong Reserved; + public ulong Timestamp; /// <summary> /// Reserved space, to be used in the future. Write as zero. /// </summary> - public ulong Reserved2; + public ulong Reserved; } /// <summary> @@ -77,9 +78,14 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache public ulong Offset; /// <summary> - /// Size. + /// Size of uncompressed data. + /// </summary> + public uint UncompressedSize; + + /// <summary> + /// Size of compressed data. /// </summary> - public uint Size; + public uint CompressedSize; } /// <summary> @@ -185,7 +191,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return 0; } - return (int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)); + return Math.Max((int)((new FileInfo(tocFilePath).Length - Unsafe.SizeOf<TocHeader>()) / sizeof(ulong)), 0); } /// <summary> @@ -324,7 +330,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache stagesBitMask = 1; } - CachedShaderStage[] shaders = new CachedShaderStage[isCompute ? 1 : Constants.ShaderStages + 1]; + GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[isCompute ? 1 : Constants.ShaderStages + 1]; DataEntryPerStage stageEntry = new DataEntryPerStage(); @@ -334,15 +340,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache dataReader.Read(ref stageEntry); - ShaderProgramInfo info = stageIndex != 0 || isCompute ? ReadShaderProgramInfo(ref dataReader) : null; - - (byte[] guestCode, byte[] cb1Data) = _guestStorage.LoadShader( + guestShaders[stageIndex] = _guestStorage.LoadShader( guestTocFileStream, guestDataFileStream, stageEntry.GuestCodeIndex); - shaders[stageIndex] = new CachedShaderStage(info, guestCode, cb1Data); - stagesBitMask &= ~(1u << stageIndex); } @@ -351,17 +353,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (loadHostCache) { - byte[] hostCode = ReadHostCode(context, ref hostTocFileStream, ref hostDataFileStream, programIndex); + (byte[] hostCode, CachedShaderStage[] shaders) = ReadHostCode( + context, + ref hostTocFileStream, + ref hostDataFileStream, + guestShaders, + programIndex, + header.Timestamp); if (hostCode != null) { bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null; int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1; - IProgram hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, new ShaderInfo(fragmentOutputMap)); + + ShaderInfo shaderInfo = specState.PipelineState.HasValue + ? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true) + : new ShaderInfo(fragmentOutputMap, fromCache: true); + + IProgram hostProgram; + + if (context.Capabilities.Api == TargetApi.Vulkan) + { + ShaderSource[] shaderSources = ShaderBinarySerializer.Unpack(shaders, hostCode, isCompute); + + hostProgram = context.Renderer.CreateProgram(shaderSources, shaderInfo); + } + else + { + hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo); + } CachedShaderProgram program = new CachedShaderProgram(hostProgram, specState, shaders); - loader.QueueHostProgram(program, hostProgram, programIndex, isCompute); + loader.QueueHostProgram(program, hostCode, programIndex, isCompute); } else { @@ -371,7 +395,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (!loadHostCache) { - loader.QueueGuestProgram(shaders, specState, programIndex, isCompute); + loader.QueueGuestProgram(guestShaders, specState, programIndex, isCompute); } loader.CheckCompilation(); @@ -393,9 +417,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <param name="context">GPU context</param> /// <param name="tocFileStream">Host TOC file stream, intialized if needed</param> /// <param name="dataFileStream">Host data file stream, initialized if needed</param> + /// <param name="guestShaders">Guest shader code for each active stage</param> /// <param name="programIndex">Index of the program on the cache</param> + /// <param name="expectedTimestamp">Timestamp of the shared cache file. The host file must be newer than it</param> /// <returns>Host binary code, or null if not found</returns> - private byte[] ReadHostCode(GpuContext context, ref Stream tocFileStream, ref Stream dataFileStream, int programIndex) + private (byte[], CachedShaderStage[]) ReadHostCode( + GpuContext context, + ref Stream tocFileStream, + ref Stream dataFileStream, + GuestCodeAndCbData?[] guestShaders, + int programIndex, + ulong expectedTimestamp) { if (tocFileStream == null && dataFileStream == null) { @@ -404,17 +436,28 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (!File.Exists(tocFilePath) || !File.Exists(dataFilePath)) { - return null; + return (null, null); } tocFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: false); dataFileStream = DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: false); + + BinarySerializer tempTocReader = new BinarySerializer(tocFileStream); + + TocHeader header = new TocHeader(); + + tempTocReader.Read(ref header); + + if (header.Timestamp < expectedTimestamp) + { + return (null, null); + } } int offset = Unsafe.SizeOf<TocHeader>() + programIndex * Unsafe.SizeOf<OffsetAndSize>(); if (offset + Unsafe.SizeOf<OffsetAndSize>() > tocFileStream.Length) { - return null; + return (null, null); } if ((ulong)offset >= (ulong)dataFileStream.Length) @@ -436,11 +479,33 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache dataFileStream.Seek((long)offsetAndSize.Offset, SeekOrigin.Begin); - byte[] hostCode = new byte[offsetAndSize.Size]; + byte[] hostCode = new byte[offsetAndSize.UncompressedSize]; BinarySerializer.ReadCompressed(dataFileStream, hostCode); - return hostCode; + CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; + BinarySerializer dataReader = new BinarySerializer(dataFileStream); + + dataFileStream.Seek((long)(offsetAndSize.Offset + offsetAndSize.CompressedSize), SeekOrigin.Begin); + + dataReader.BeginCompression(); + + for (int index = 0; index < guestShaders.Length; index++) + { + if (!guestShaders[index].HasValue) + { + continue; + } + + GuestCodeAndCbData guestShader = guestShaders[index].Value; + ShaderProgramInfo info = index != 0 || guestShaders.Length == 1 ? ReadShaderProgramInfo(ref dataReader) : null; + + shaders[index] = new CachedShaderStage(info, guestShader.Code, guestShader.Cb1Data); + } + + dataReader.EndCompression(); + + return (hostCode, shaders); } /// <summary> @@ -484,10 +549,12 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache var tocFileStream = streams != null ? streams.TocFileStream : DiskCacheCommon.OpenFile(_basePath, SharedTocFileName, writable: true); var dataFileStream = streams != null ? streams.DataFileStream : DiskCacheCommon.OpenFile(_basePath, SharedDataFileName, writable: true); + ulong timestamp = (ulong)DateTime.UtcNow.Subtract(DateTime.UnixEpoch).TotalSeconds; + if (tocFileStream.Length == 0) { TocHeader header = new TocHeader(); - CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion); + CreateToc(tocFileStream, ref header, TocsMagic, CodeGenVersion, timestamp); } tocFileStream.Seek(0, SeekOrigin.End); @@ -519,8 +586,6 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache stageEntry.GuestCodeIndex = _guestStorage.AddShader(shader.Code, shader.Cb1Data); dataWriter.Write(ref stageEntry); - - WriteShaderProgramInfo(ref dataWriter, shader.Info); } program.SpecializationState.Write(ref dataWriter); @@ -537,7 +602,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache return; } - WriteHostCode(context, hostCode, -1, streams); + WriteHostCode(context, hostCode, program.Shaders, streams, timestamp); } /// <summary> @@ -575,28 +640,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } /// <summary> - /// Adds a host binary shader to the host cache. - /// </summary> - /// <remarks> - /// This only modifies the host cache. The shader must already exist in the other caches. - /// This method should only be used for rebuilding the host cache after a clear. - /// </remarks> - /// <param name="context">GPU context</param> - /// <param name="hostCode">Host binary code</param> - /// <param name="programIndex">Index of the program in the cache</param> - public void AddHostShader(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex) - { - WriteHostCode(context, hostCode, programIndex); - } - - /// <summary> /// Writes the host binary code on the host cache. /// </summary> /// <param name="context">GPU context</param> /// <param name="hostCode">Host binary code</param> - /// <param name="programIndex">Index of the program in the cache</param> + /// <param name="shaders">Shader stages to be added to the host cache</param> /// <param name="streams">Output streams to use</param> - private void WriteHostCode(GpuContext context, ReadOnlySpan<byte> hostCode, int programIndex, DiskCacheOutputStreams streams = null) + /// <param name="timestamp">File creation timestamp</param> + private void WriteHostCode( + GpuContext context, + ReadOnlySpan<byte> hostCode, + CachedShaderStage[] shaders, + DiskCacheOutputStreams streams, + ulong timestamp) { var tocFileStream = streams != null ? streams.HostTocFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostTocFileName(context), writable: true); var dataFileStream = streams != null ? streams.HostDataFileStream : DiskCacheCommon.OpenFile(_basePath, GetHostDataFileName(context), writable: true); @@ -604,29 +660,39 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache if (tocFileStream.Length == 0) { TocHeader header = new TocHeader(); - CreateToc(tocFileStream, ref header, TochMagic, 0); - } - - if (programIndex == -1) - { - tocFileStream.Seek(0, SeekOrigin.End); - } - else - { - tocFileStream.Seek(Unsafe.SizeOf<TocHeader>() + (programIndex * Unsafe.SizeOf<OffsetAndSize>()), SeekOrigin.Begin); + CreateToc(tocFileStream, ref header, TochMagic, 0, timestamp); } + tocFileStream.Seek(0, SeekOrigin.End); dataFileStream.Seek(0, SeekOrigin.End); BinarySerializer tocWriter = new BinarySerializer(tocFileStream); + BinarySerializer dataWriter = new BinarySerializer(dataFileStream); OffsetAndSize offsetAndSize = new OffsetAndSize(); offsetAndSize.Offset = (ulong)dataFileStream.Position; - offsetAndSize.Size = (uint)hostCode.Length; - tocWriter.Write(ref offsetAndSize); + offsetAndSize.UncompressedSize = (uint)hostCode.Length; + + long dataStartPosition = dataFileStream.Position; BinarySerializer.WriteCompressed(dataFileStream, hostCode, DiskCacheCommon.GetCompressionAlgorithm()); + offsetAndSize.CompressedSize = (uint)(dataFileStream.Position - dataStartPosition); + + tocWriter.Write(ref offsetAndSize); + + dataWriter.BeginCompression(DiskCacheCommon.GetCompressionAlgorithm()); + + for (int index = 0; index < shaders.Length; index++) + { + if (shaders[index] != null) + { + WriteShaderProgramInfo(ref dataWriter, shaders[index].Info); + } + } + + dataWriter.EndCompression(); + if (streams == null) { tocFileStream.Dispose(); @@ -641,7 +707,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <param name="header">Set to the TOC file header</param> /// <param name="magic">Magic value to be written</param> /// <param name="codegenVersion">Shader codegen version, only valid for the host file</param> - private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion) + /// <param name="timestamp">File creation timestamp</param> + private void CreateToc(Stream tocFileStream, ref TocHeader header, uint magic, uint codegenVersion, ulong timestamp) { BinarySerializer writer = new BinarySerializer(tocFileStream); @@ -650,7 +717,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache header.CodeGenVersion = codegenVersion; header.Padding = 0; header.Reserved = 0; - header.Reserved2 = 0; + header.Timestamp = timestamp; if (tocFileStream.Length > 0) { diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs new file mode 100644 index 00000000..b1ac819e --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/GuestCodeAndCbData.cs @@ -0,0 +1,31 @@ +using Ryujinx.Graphics.Shader; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + /// <summary> + /// Guest shader code and constant buffer data accessed by the shader. + /// </summary> + struct GuestCodeAndCbData + { + /// <summary> + /// Maxwell binary shader code. + /// </summary> + public byte[] Code { get; } + + /// <summary> + /// Constant buffer 1 data accessed by the shader. + /// </summary> + public byte[] Cb1Data { get; } + + /// <summary> + /// Creates a new instance of the guest shader code and constant buffer data. + /// </summary> + /// <param name="code">Maxwell binary shader code</param> + /// <param name="cb1Data">Constant buffer 1 data accessed by the shader</param> + public GuestCodeAndCbData(byte[] code, byte[] cb1Data) + { + Code = code; + Cb1Data = cb1Data; + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs index af7579d5..7bf1cf4b 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ParallelDiskCacheLoader.cs @@ -45,9 +45,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache public readonly CachedShaderProgram CachedProgram; /// <summary> - /// Host program. + /// Optional binary code. If not null, it is used instead of the backend host binary. /// </summary> - public readonly IProgram HostProgram; + public readonly byte[] BinaryCode; /// <summary> /// Program index. @@ -68,19 +68,19 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// Creates a new program validation entry. /// </summary> /// <param name="cachedProgram">Cached shader program</param> - /// <param name="hostProgram">Host program</param> + /// <param name="binaryCode">Optional binary code. If not null, it is used instead of the backend host binary</param> /// <param name="programIndex">Program index</param> /// <param name="isCompute">Indicates if the program is a compute shader</param> /// <param name="isBinary">Indicates if the program is a host binary shader</param> public ProgramEntry( CachedShaderProgram cachedProgram, - IProgram hostProgram, + byte[] binaryCode, int programIndex, bool isCompute, bool isBinary) { CachedProgram = cachedProgram; - HostProgram = hostProgram; + BinaryCode = binaryCode; ProgramIndex = programIndex; IsCompute = isCompute; IsBinary = isBinary; @@ -146,9 +146,9 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private struct AsyncProgramTranslation { /// <summary> - /// Cached shader stages. + /// Guest code for each active stage. /// </summary> - public readonly CachedShaderStage[] Shaders; + public readonly GuestCodeAndCbData?[] GuestShaders; /// <summary> /// Specialization state. @@ -168,17 +168,17 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <summary> /// Creates a new program translation entry. /// </summary> - /// <param name="shaders">Cached shader stages</param> + /// <param name="guestShaders">Guest code for each active stage</param> /// <param name="specState">Specialization state</param> /// <param name="programIndex">Program index</param> /// <param name="isCompute">Indicates if the program is a compute shader</param> public AsyncProgramTranslation( - CachedShaderStage[] shaders, + GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute) { - Shaders = shaders; + GuestShaders = guestShaders; SpecializationState = specState; ProgramIndex = programIndex; IsCompute = isCompute; @@ -188,7 +188,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache private readonly Queue<ProgramEntry> _validationQueue; private readonly ConcurrentQueue<ProgramCompilation> _compilationQueue; private readonly BlockingCollection<AsyncProgramTranslation> _asyncTranslationQueue; - private readonly SortedList<int, CachedShaderProgram> _programList; + private readonly SortedList<int, (CachedShaderProgram, byte[])> _programList; private int _backendParallelCompileThreads; private int _compiledCount; @@ -220,7 +220,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache _validationQueue = new Queue<ProgramEntry>(); _compilationQueue = new ConcurrentQueue<ProgramCompilation>(); _asyncTranslationQueue = new BlockingCollection<AsyncProgramTranslation>(ThreadCount); - _programList = new SortedList<int, CachedShaderProgram>(); + _programList = new SortedList<int, (CachedShaderProgram, byte[])>(); _backendParallelCompileThreads = Math.Min(Environment.ProcessorCount, 8); // Must be kept in sync with the backend code. } @@ -235,7 +235,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { workThreads[index] = new Thread(ProcessAsyncQueue) { - Name = $"Gpu.AsyncTranslationThread.{index}" + Name = $"GPU.AsyncTranslationThread.{index}" }; } @@ -287,7 +287,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache CheckCompilationBlocking(); - if (_needsHostRegen) + if (_needsHostRegen && Active) { // Rebuild both shared and host cache files. // Rebuilding shared is required because the shader information returned by the translator @@ -310,8 +310,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache break; } - CachedShaderProgram program = kv.Value; - _hostStorage.AddShader(_context, program, program.HostProgram.GetBinary(), streams); + (CachedShaderProgram program, byte[] binaryCode) = kv.Value; + _hostStorage.AddShader(_context, program, binaryCode, streams); } Logger.Info?.Print(LogClass.Gpu, $"Rebuilt {_programList.Count} shaders successfully."); @@ -342,24 +342,31 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// Enqueues a host program for compilation. /// </summary> /// <param name="cachedProgram">Cached program</param> - /// <param name="hostProgram">Host program to be compiled</param> + /// <param name="binaryCode">Host binary code</param> /// <param name="programIndex">Program index</param> /// <param name="isCompute">Indicates if the program is a compute shader</param> - public void QueueHostProgram(CachedShaderProgram cachedProgram, IProgram hostProgram, int programIndex, bool isCompute) + public void QueueHostProgram(CachedShaderProgram cachedProgram, byte[] binaryCode, int programIndex, bool isCompute) { - EnqueueForValidation(new ProgramEntry(cachedProgram, hostProgram, programIndex, isCompute, isBinary: true)); + EnqueueForValidation(new ProgramEntry(cachedProgram, binaryCode, programIndex, isCompute, isBinary: true)); } /// <summary> /// Enqueues a guest program for compilation. /// </summary> - /// <param name="shaders">Cached shader stages</param> + /// <param name="guestShaders">Guest code for each active stage</param> /// <param name="specState">Specialization state</param> /// <param name="programIndex">Program index</param> /// <param name="isCompute">Indicates if the program is a compute shader</param> - public void QueueGuestProgram(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute) + public void QueueGuestProgram(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute) { - _asyncTranslationQueue.Add(new AsyncProgramTranslation(shaders, specState, programIndex, isCompute)); + try + { + AsyncProgramTranslation asyncTranslation = new AsyncProgramTranslation(guestShaders, specState, programIndex, isCompute); + _asyncTranslationQueue.Add(asyncTranslation, _cancellationToken); + } + catch (OperationCanceledException) + { + } } /// <summary> @@ -374,7 +381,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache // If not yet compiled, do nothing. This avoids blocking to wait for shader compilation. while (_validationQueue.TryPeek(out ProgramEntry entry)) { - ProgramLinkStatus result = entry.HostProgram.CheckProgramLink(false); + ProgramLinkStatus result = entry.CachedProgram.HostProgram.CheckProgramLink(false); if (result != ProgramLinkStatus.Incomplete) { @@ -398,7 +405,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache while (_validationQueue.TryDequeue(out ProgramEntry entry) && Active) { - ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false); + ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false); } } @@ -427,7 +434,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache _needsHostRegen = true; } - _programList.Add(entry.ProgramIndex, entry.CachedProgram); + _programList.Add(entry.ProgramIndex, (entry.CachedProgram, entry.BinaryCode)); SignalCompiled(); } else if (entry.IsBinary) @@ -436,13 +443,25 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache // we still have a chance to recompile from the guest binary. CachedShaderProgram program = entry.CachedProgram; + GuestCodeAndCbData?[] guestShaders = new GuestCodeAndCbData?[program.Shaders.Length]; + + for (int index = 0; index < program.Shaders.Length; index++) + { + CachedShaderStage shader = program.Shaders[index]; + + if (shader != null) + { + guestShaders[index] = new GuestCodeAndCbData(shader.Code, shader.Cb1Data); + } + } + if (asyncCompile) { - QueueGuestProgram(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); + QueueGuestProgram(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); } else { - RecompileFromGuestCode(program.Shaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); + RecompileFromGuestCode(guestShaders, program.SpecializationState, entry.ProgramIndex, entry.IsCompute); ProcessCompilationQueue(); } } @@ -476,10 +495,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } } - IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, new ShaderInfo(fragmentOutputMap)); + ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue + ? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true) + : new ShaderInfo(fragmentOutputMap, fromCache: true); + + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo); CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders); - EnqueueForValidation(new ProgramEntry(program, hostProgram, compilation.ProgramIndex, compilation.IsCompute, isBinary: false)); + byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(shaderSources) : hostProgram.GetBinary(); + + EnqueueForValidation(new ProgramEntry(program, binaryCode, compilation.ProgramIndex, compilation.IsCompute, isBinary: false)); } } @@ -496,7 +521,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache // Submitting more seems to cause NVIDIA OpenGL driver to crash. if (_validationQueue.Count >= _backendParallelCompileThreads && _validationQueue.TryDequeue(out ProgramEntry entry)) { - ProcessCompiledProgram(ref entry, entry.HostProgram.CheckProgramLink(true), asyncCompile: false); + ProcessCompiledProgram(ref entry, entry.CachedProgram.HostProgram.CheckProgramLink(true), asyncCompile: false); } } @@ -513,7 +538,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache foreach (AsyncProgramTranslation asyncCompilation in _asyncTranslationQueue.GetConsumingEnumerable(ct)) { RecompileFromGuestCode( - asyncCompilation.Shaders, + asyncCompilation.GuestShaders, asyncCompilation.SpecializationState, asyncCompilation.ProgramIndex, asyncCompilation.IsCompute); @@ -527,21 +552,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <summary> /// Recompiles a program from guest code. /// </summary> - /// <param name="shaders">Shader stages</param> + /// <param name="guestShaders">Guest code for each active stage</param> /// <param name="specState">Specialization state</param> /// <param name="programIndex">Program index</param> /// <param name="isCompute">Indicates if the program is a compute shader</param> - private void RecompileFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex, bool isCompute) + private void RecompileFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex, bool isCompute) { try { if (isCompute) { - RecompileComputeFromGuestCode(shaders, specState, programIndex); + RecompileComputeFromGuestCode(guestShaders, specState, programIndex); } else { - RecompileGraphicsFromGuestCode(shaders, specState, programIndex); + RecompileGraphicsFromGuestCode(guestShaders, specState, programIndex); } } catch (DiskCacheLoadException diskCacheLoadException) @@ -556,41 +581,47 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <summary> /// Recompiles a graphics program from guest code. /// </summary> - /// <param name="shaders">Shader stages</param> + /// <param name="guestShaders">Guest code for each active stage</param> /// <param name="specState">Specialization state</param> /// <param name="programIndex">Program index</param> - private void RecompileGraphicsFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex) + private void RecompileGraphicsFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex) { - ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.GraphicsState, specState.TransformFeedbackDescriptors); + ShaderSpecializationState newSpecState = new ShaderSpecializationState( + ref specState.GraphicsState, + specState.PipelineState, + specState.TransformFeedbackDescriptors); + ResourceCounts counts = new ResourceCounts(); TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; + TargetApi api = _context.Capabilities.Api; + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) { - CachedShaderStage shader = shaders[stageIndex + 1]; - - if (shader != null) + if (guestShaders[stageIndex + 1].HasValue) { + GuestCodeAndCbData shader = guestShaders[stageIndex + 1].Value; + byte[] guestCode = shader.Code; byte[] cb1Data = shader.Cb1Data; DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, guestCode, cb1Data, specState, newSpecState, counts, stageIndex); - TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, 0); + TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, 0); if (nextStage != null) { currentStage.SetNextStage(nextStage); } - if (stageIndex == 0 && shaders[0] != null) + if (stageIndex == 0 && guestShaders[0].HasValue) { - byte[] guestCodeA = shaders[0].Code; - byte[] cb1DataA = shaders[0].Cb1Data; + byte[] guestCodeA = guestShaders[0].Value.Code; + byte[] cb1DataA = guestShaders[0].Value.Cb1Data; DiskCacheGpuAccessor gpuAccessorA = new DiskCacheGpuAccessor(_context, guestCodeA, cb1DataA, specState, newSpecState, counts, 0); - translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, DefaultFlags | TranslationFlags.VertexA, 0); + translatorContexts[0] = DecodeGraphicsShader(gpuAccessorA, api, DefaultFlags | TranslationFlags.VertexA, 0); } translatorContexts[stageIndex + 1] = currentStage; @@ -598,6 +629,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache } } + CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length]; List<ShaderProgram> translatedStages = new List<ShaderProgram>(); for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++) @@ -608,15 +640,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { ShaderProgram program; - byte[] guestCode = shaders[stageIndex + 1].Code; - byte[] cb1Data = shaders[stageIndex + 1].Cb1Data; + byte[] guestCode = guestShaders[stageIndex + 1].Value.Code; + byte[] cb1Data = guestShaders[stageIndex + 1].Value.Cb1Data; - if (stageIndex == 0 && shaders[0] != null) + if (stageIndex == 0 && guestShaders[0].HasValue) { program = currentStage.Translate(translatorContexts[0]); - byte[] guestCodeA = shaders[0].Code; - byte[] cb1DataA = shaders[0].Cb1Data; + byte[] guestCodeA = guestShaders[0].Value.Code; + byte[] cb1DataA = guestShaders[0].Value.Cb1Data; shaders[0] = new CachedShaderStage(null, guestCodeA, cb1DataA); shaders[1] = new CachedShaderStage(program.Info, guestCode, cb1Data); @@ -641,21 +673,21 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache /// <summary> /// Recompiles a compute program from guest code. /// </summary> - /// <param name="shaders">Shader stages</param> + /// <param name="guestShaders">Guest code for each active stage</param> /// <param name="specState">Specialization state</param> /// <param name="programIndex">Program index</param> - private void RecompileComputeFromGuestCode(CachedShaderStage[] shaders, ShaderSpecializationState specState, int programIndex) + private void RecompileComputeFromGuestCode(GuestCodeAndCbData?[] guestShaders, ShaderSpecializationState specState, int programIndex) { - CachedShaderStage shader = shaders[0]; + GuestCodeAndCbData shader = guestShaders[0].Value; ResourceCounts counts = new ResourceCounts(); - ShaderSpecializationState newSpecState = new ShaderSpecializationState(specState.ComputeState); + ShaderSpecializationState newSpecState = new ShaderSpecializationState(ref specState.ComputeState); DiskCacheGpuAccessor gpuAccessor = new DiskCacheGpuAccessor(_context, shader.Code, shader.Cb1Data, specState, newSpecState, counts, 0); - TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, 0); + TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, 0); ShaderProgram program = translatorContext.Translate(); - shaders[0] = new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data); + CachedShaderStage[] shaders = new[] { new CachedShaderStage(program.Info, shader.Code, shader.Cb1Data) }; _compilationQueue.Enqueue(new ProgramCompilation(new[] { program }, shaders, newSpecState, programIndex, isCompute: true)); } diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs new file mode 100644 index 00000000..11e54220 --- /dev/null +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs @@ -0,0 +1,49 @@ +using Ryujinx.Graphics.GAL; +using Ryujinx.Graphics.Shader.Translation; +using System.Collections.Generic; +using System.IO; + +namespace Ryujinx.Graphics.Gpu.Shader.DiskCache +{ + static class ShaderBinarySerializer + { + public static byte[] Pack(ShaderSource[] sources) + { + using MemoryStream output = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(output); + + for (int i = 0; i < sources.Length; i++) + { + writer.Write(sources[i].BinaryCode.Length); + writer.Write(sources[i].BinaryCode); + } + + return output.ToArray(); + } + + public static ShaderSource[] Unpack(CachedShaderStage[] stages, byte[] code, bool compute) + { + using MemoryStream input = new MemoryStream(code); + using BinaryReader reader = new BinaryReader(input); + + List<ShaderSource> output = new List<ShaderSource>(); + + for (int i = compute ? 0 : 1; i < stages.Length; i++) + { + CachedShaderStage stage = stages[i]; + + if (stage == null) + { + continue; + } + + int binaryCodeLength = reader.ReadInt32(); + byte[] binaryCode = reader.ReadBytes(binaryCodeLength); + + output.Add(new ShaderSource(binaryCode, ShaderCache.GetBindings(stage.Info), stage.Info.Stage, TargetLanguage.Spirv)); + } + + return output.ToArray(); + } + } +}
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs index 5317aab9..44c26efb 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs @@ -1,6 +1,8 @@ using Ryujinx.Common.Logging; +using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; using System; using System.Runtime.InteropServices; @@ -15,6 +17,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private readonly GpuAccessorState _state; private readonly int _stageIndex; private readonly bool _compute; + private readonly bool _isVulkan; /// <summary> /// Creates a new instance of the GPU state accessor for graphics shader translation. @@ -23,8 +26,13 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="channel">GPU channel</param> /// <param name="state">Current GPU state</param> /// <param name="stageIndex">Graphics shader stage index (0 = Vertex, 4 = Fragment)</param> - public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state, int stageIndex) : base(context) + public GpuAccessor( + GpuContext context, + GpuChannel channel, + GpuAccessorState state, + int stageIndex) : base(context, state.ResourceCounts, stageIndex) { + _isVulkan = context.Capabilities.Api == TargetApi.Vulkan; _channel = channel; _state = state; _stageIndex = stageIndex; @@ -36,7 +44,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="context">GPU context</param> /// <param name="channel">GPU channel</param> /// <param name="state">Current GPU state</param> - public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context) + public GpuAccessor(GpuContext context, GpuChannel channel, GpuAccessorState state) : base(context, state.ResourceCounts, 0) { _channel = channel; _state = state; @@ -73,27 +81,36 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <inheritdoc/> - public int QueryBindingConstantBuffer(int index) + public AlphaTestOp QueryAlphaTestCompare() { - return _state.ResourceCounts.UniformBuffersCount++; - } + if (!_isVulkan || !_state.GraphicsState.AlphaTestEnable) + { + return AlphaTestOp.Always; + } - /// <inheritdoc/> - public int QueryBindingStorageBuffer(int index) - { - return _state.ResourceCounts.StorageBuffersCount++; + return _state.GraphicsState.AlphaTestCompare switch + { + CompareOp.Never or CompareOp.NeverGl => AlphaTestOp.Never, + CompareOp.Less or CompareOp.LessGl => AlphaTestOp.Less, + CompareOp.Equal or CompareOp.EqualGl => AlphaTestOp.Equal, + CompareOp.LessOrEqual or CompareOp.LessOrEqualGl => AlphaTestOp.LessOrEqual, + CompareOp.Greater or CompareOp.GreaterGl => AlphaTestOp.Greater, + CompareOp.NotEqual or CompareOp.NotEqualGl => AlphaTestOp.NotEqual, + CompareOp.GreaterOrEqual or CompareOp.GreaterOrEqualGl => AlphaTestOp.GreaterOrEqual, + _ => AlphaTestOp.Always + }; } /// <inheritdoc/> - public int QueryBindingTexture(int index) + public float QueryAlphaTestReference() { - return _state.ResourceCounts.TexturesCount++; + return _state.GraphicsState.AlphaTestReference; } /// <inheritdoc/> - public int QueryBindingImage(int index) + public AttributeType QueryAttributeType(int location) { - return _state.ResourceCounts.ImagesCount++; + return _state.GraphicsState.AttributeTypes[location]; } /// <inheritdoc/> @@ -130,6 +147,18 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <inheritdoc/> + public bool QueryProgramPointSize() + { + return _state.GraphicsState.ProgramPointSizeEnable; + } + + /// <inheritdoc/> + public float QueryPointSize() + { + return _state.GraphicsState.PointSize; + } + + /// <inheritdoc/> public bool QueryTessCw() { return _state.GraphicsState.TessellationMode.UnpackCw(); @@ -199,6 +228,12 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <inheritdoc/> + public bool QueryTransformDepthMinusOneToOne() + { + return _state.GraphicsState.DepthMode; + } + + /// <inheritdoc/> public bool QueryTransformFeedbackEnabled() { return _state.TransformFeedbackDescriptors != null; diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs index 5f9dd588..7243f643 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessorBase.cs @@ -1,7 +1,9 @@ +using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Threed; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Shader; +using Ryujinx.Graphics.Shader.Translation; namespace Ryujinx.Graphics.Gpu.Shader { @@ -11,74 +13,140 @@ namespace Ryujinx.Graphics.Gpu.Shader class GpuAccessorBase { private readonly GpuContext _context; + private readonly ResourceCounts _resourceCounts; + private readonly int _stageIndex; /// <summary> /// Creates a new GPU accessor. /// </summary> /// <param name="context">GPU context</param> - public GpuAccessorBase(GpuContext context) + public GpuAccessorBase(GpuContext context, ResourceCounts resourceCounts, int stageIndex) { _context = context; + _resourceCounts = resourceCounts; + _stageIndex = stageIndex; } - /// <summary> - /// Queries host about the presence of the FrontFacing built-in variable bug. - /// </summary> - /// <returns>True if the bug is present on the host device used, false otherwise</returns> + /// <inheritdoc/> + public int QueryBindingConstantBuffer(int index) + { + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + // We need to start counting from 1 since binding 0 is reserved for the support uniform buffer. + return GetBindingFromIndex(index, _context.Capabilities.MaximumUniformBuffersPerStage, "Uniform buffer") + 1; + } + else + { + return _resourceCounts.UniformBuffersCount++; + } + } + + /// <inheritdoc/> + public int QueryBindingStorageBuffer(int index) + { + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + return GetBindingFromIndex(index, _context.Capabilities.MaximumStorageBuffersPerStage, "Storage buffer"); + } + else + { + return _resourceCounts.StorageBuffersCount++; + } + } + + /// <inheritdoc/> + public int QueryBindingTexture(int index, bool isBuffer) + { + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumTexturesPerStage; + } + + return GetBindingFromIndex(index, _context.Capabilities.MaximumTexturesPerStage * 2, "Texture"); + } + else + { + return _resourceCounts.TexturesCount++; + } + } + + /// <inheritdoc/> + public int QueryBindingImage(int index, bool isBuffer) + { + if (_context.Capabilities.Api == TargetApi.Vulkan) + { + if (isBuffer) + { + index += (int)_context.Capabilities.MaximumImagesPerStage; + } + + return GetBindingFromIndex(index, _context.Capabilities.MaximumImagesPerStage * 2, "Image"); + } + else + { + return _resourceCounts.ImagesCount++; + } + } + + private int GetBindingFromIndex(int index, uint maxPerStage, string resourceName) + { + if ((uint)index >= maxPerStage) + { + Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}."); + } + + return GetStageIndex() * (int)maxPerStage + index; + } + + private int GetStageIndex() + { + // This is just a simple remapping to ensure that most frequently used shader stages + // have the lowest binding numbers. + // This is useful because if we need to run on a system with a low limit on the bindings, + // then we can still get most games working as the most common shaders will have low binding numbers. + return _stageIndex switch + { + 4 => 1, // Fragment + 3 => 2, // Geometry + 1 => 3, // Tessellation control + 2 => 4, // Tessellation evaluation + _ => 0 // Vertex/Compute + }; + } + + /// <inheritdoc/> public bool QueryHostHasFrontFacingBug() => _context.Capabilities.HasFrontFacingBug; - /// <summary> - /// Queries host about the presence of the vector indexing bug. - /// </summary> - /// <returns>True if the bug is present on the host device used, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostHasVectorIndexingBug() => _context.Capabilities.HasVectorIndexingBug; - /// <summary> - /// Queries host storage buffer alignment required. - /// </summary> - /// <returns>Host storage buffer alignment in bytes</returns> + /// <inheritdoc/> public int QueryHostStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment; - /// <summary> - /// Queries host support for texture formats with BGRA component order (such as BGRA8). - /// </summary> - /// <returns>True if BGRA formats are supported, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsBgraFormat() => _context.Capabilities.SupportsBgraFormat; - /// <summary> - /// Queries host support for fragment shader ordering critical sections on the shader code. - /// </summary> - /// <returns>True if fragment shader interlock is supported, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsFragmentShaderInterlock() => _context.Capabilities.SupportsFragmentShaderInterlock; - /// <summary> - /// Queries host support for fragment shader ordering scoped critical sections on the shader code. - /// </summary> - /// <returns>True if fragment shader ordering is supported, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel; - /// <summary> - /// Queries host support for readable images without a explicit format declaration on the shader. - /// </summary> - /// <returns>True if formatted image load is supported, false otherwise</returns> + /// <inheritdoc/> + public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough; + + /// <inheritdoc/> public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted; - /// <summary> - /// Queries host GPU non-constant texture offset support. - /// </summary> - /// <returns>True if the GPU and driver supports non-constant texture offsets, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset; - /// <summary> - /// Queries host GPU shader ballot support. - /// </summary> - /// <returns>True if the GPU and driver supports shader ballot, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot; - /// <summary> - /// Queries host GPU texture shadow LOD support. - /// </summary> - /// <returns>True if the GPU and driver supports texture shadow LOD, false otherwise</returns> + /// <inheritdoc/> public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod; /// <summary> diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs index fae670ea..82252ced 100644 --- a/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/GpuChannelGraphicsState.cs @@ -1,5 +1,7 @@ +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Shader; namespace Ryujinx.Graphics.Gpu.Shader { @@ -26,19 +28,54 @@ namespace Ryujinx.Graphics.Gpu.Shader public readonly TessMode TessellationMode; /// <summary> - /// Indicates whenever the viewport transform is disabled. + /// Indicates whether alpha-to-coverage is enabled. + /// </summary> + public readonly bool AlphaToCoverageEnable; + + /// <summary> + /// Indicates whether alpha-to-coverage dithering is enabled. + /// </summary> + public readonly bool AlphaToCoverageDitherEnable; + + /// <summary> + /// Indicates whether the viewport transform is disabled. /// </summary> public readonly bool ViewportTransformDisable; /// <summary> - /// Indicates whenever alpha-to-coverage is enabled. + /// Depth mode zero to one or minus one to one. /// </summary> - public readonly bool AlphaToCoverageEnable; + public readonly bool DepthMode; /// <summary> - /// Indicates whenever alpha-to-coverage dithering is enabled. + /// Indicates if the point size is set on the shader or is fixed. /// </summary> - public readonly bool AlphaToCoverageDitherEnable; + public readonly bool ProgramPointSizeEnable; + + /// <summary> + /// Point size used if <see cref="ProgramPointSizeEnable" /> is false. + /// </summary> + public readonly float PointSize; + + /// <summary> + /// Indicates whether alpha test is enabled. + /// </summary> + public readonly bool AlphaTestEnable; + + /// <summary> + /// When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded. + /// </summary> + public readonly CompareOp AlphaTestCompare; + + /// <summary> + /// When alpha test is enabled, indicates the value to compare with the fragment output alpha. + /// </summary> + public readonly float AlphaTestReference; + + /// <summary> + /// Type of the vertex attributes consumed by the shader. + /// </summary> + public Array32<AttributeType> AttributeTypes; /// <summary> /// Creates a new GPU graphics state. @@ -46,23 +83,44 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <param name="earlyZForce">Early Z force enable</param> /// <param name="topology">Primitive topology</param> /// <param name="tessellationMode">Tessellation mode</param> - /// <param name="viewportTransformDisable">Indicates whenever the viewport transform is disabled</param> - /// <param name="alphaToCoverageEnable">Indicates whenever alpha-to-coverage is enabled</param> - /// <param name="alphaToCoverageDitherEnable">Indicates whenever alpha-to-coverage dithering is enabled</param> + /// <param name="alphaToCoverageEnable">Indicates whether alpha-to-coverage is enabled</param> + /// <param name="alphaToCoverageDitherEnable">Indicates whether alpha-to-coverage dithering is enabled</param> + /// <param name="viewportTransformDisable">Indicates whether the viewport transform is disabled</param> + /// <param name="depthMode">Depth mode zero to one or minus one to one</param> + /// <param name="programPointSizeEnable">Indicates if the point size is set on the shader or is fixed</param> + /// <param name="pointSize">Point size if not set from shader</param> + /// <param name="alphaTestEnable">Indicates whether alpha test is enabled</param> + /// <param name="alphaTestCompare">When alpha test is enabled, indicates the comparison that decides if the fragment should be discarded</param> + /// <param name="alphaTestReference">When alpha test is enabled, indicates the value to compare with the fragment output alpha</param> + /// <param name="attributeTypes">Type of the vertex attributes consumed by the shader</param> public GpuChannelGraphicsState( bool earlyZForce, PrimitiveTopology topology, TessMode tessellationMode, - bool viewportTransformDisable, bool alphaToCoverageEnable, - bool alphaToCoverageDitherEnable) + bool alphaToCoverageDitherEnable, + bool viewportTransformDisable, + bool depthMode, + bool programPointSizeEnable, + float pointSize, + bool alphaTestEnable, + CompareOp alphaTestCompare, + float alphaTestReference, + ref Array32<AttributeType> attributeTypes) { EarlyZForce = earlyZForce; Topology = topology; TessellationMode = tessellationMode; - ViewportTransformDisable = viewportTransformDisable; AlphaToCoverageEnable = alphaToCoverageEnable; AlphaToCoverageDitherEnable = alphaToCoverageDitherEnable; + ViewportTransformDisable = viewportTransformDisable; + DepthMode = depthMode; + ProgramPointSizeEnable = programPointSizeEnable; + PointSize = pointSize; + AlphaTestEnable = alphaTestEnable; + AlphaTestCompare = alphaTestCompare; + AlphaTestReference = alphaTestReference; + AttributeTypes = attributeTypes; } } }
\ No newline at end of file diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs index 0779bf2c..c998fe09 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs @@ -1,13 +1,17 @@ +using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Engine.Threed; +using Ryujinx.Graphics.Gpu.Engine.Types; +using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; -using Ryujinx.Graphics.Gpu.Shader.Cache; using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading; namespace Ryujinx.Graphics.Gpu.Shader @@ -59,11 +63,13 @@ namespace Ryujinx.Graphics.Gpu.Shader { public readonly CachedShaderProgram CachedProgram; public readonly IProgram HostProgram; + public readonly byte[] BinaryCode; - public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram) + public ProgramToSave(CachedShaderProgram cachedProgram, IProgram hostProgram, byte[] binaryCode) { CachedProgram = cachedProgram; HostProgram = hostProgram; + BinaryCode = binaryCode; } } @@ -94,9 +100,7 @@ namespace Ryujinx.Graphics.Gpu.Shader _programsToSaveQueue = new Queue<ProgramToSave>(); - string diskCacheTitleId = GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null - ? CacheHelper.GetBaseCacheDirectory(GraphicsConfig.TitleId) - : null; + string diskCacheTitleId = GetDiskCachePath(); _computeShaderCache = new ComputeShaderCacheHashTable(); _graphicsShaderCache = new ShaderCacheHashTable(); @@ -109,6 +113,16 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Gets the path where the disk cache for the current application is stored. + /// </summary> + private static string GetDiskCachePath() + { + return GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null + ? Path.Combine(AppDataManager.GamesDirPath, GraphicsConfig.TitleId, "cache", "shader") + : null; + } + + /// <summary> /// Processes the queue of shaders that must save their binaries to the disk cache. /// </summary> public void ProcessShaderCacheQueue() @@ -123,7 +137,7 @@ namespace Ryujinx.Graphics.Gpu.Shader { if (result == ProgramLinkStatus.Success) { - _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.HostProgram.GetBinary()); + _cacheWriter.AddShader(programToSave.CachedProgram, programToSave.BinaryCode ?? programToSave.HostProgram.GetBinary()); } _programsToSaveQueue.Dequeue(); @@ -143,16 +157,6 @@ namespace Ryujinx.Graphics.Gpu.Shader { if (_diskCacheHostStorage.CacheEnabled) { - if (!_diskCacheHostStorage.CacheExists()) - { - // If we don't have a shader cache on the new format, try to perform migration from the old shader cache. - Logger.Info?.Print(LogClass.Gpu, "No shader cache found, trying to migrate from legacy shader cache..."); - - int migrationCount = Migration.MigrateFromLegacyCache(_context, _diskCacheHostStorage); - - Logger.Info?.Print(LogClass.Gpu, $"Migrated {migrationCount} shaders."); - } - ParallelDiskCacheLoader loader = new ParallelDiskCacheLoader( _context, _graphicsShaderCache, @@ -210,26 +214,75 @@ namespace Ryujinx.Graphics.Gpu.Shader return cpShader; } - ShaderSpecializationState specState = new ShaderSpecializationState(computeState); + ShaderSpecializationState specState = new ShaderSpecializationState(ref computeState); GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, computeState, default, specState); GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState); - TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, gpuVa); + TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa); TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode); - IProgram hostProgram = _context.Renderer.CreateProgram(new ShaderSource[] { CreateShaderSource(translatedShader.Program) }, new ShaderInfo(-1)); + ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) }; + + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1)); cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader); _computeShaderCache.Add(cpShader); - EnqueueProgramToSave(new ProgramToSave(cpShader, hostProgram)); + EnqueueProgramToSave(cpShader, hostProgram, shaderSourcesArray); _cpPrograms[gpuVa] = cpShader; return cpShader; } /// <summary> + /// Updates the shader pipeline state based on the current GPU state. + /// </summary> + /// <param name="state">Current GPU 3D engine state</param> + /// <param name="pipeline">Shader pipeline state to be updated</param> + /// <param name="graphicsState">Current graphics state</param> + /// <param name="channel">Current GPU channel</param> + private void UpdatePipelineInfo( + ref ThreedClassState state, + ref ProgramPipelineState pipeline, + GpuChannelGraphicsState graphicsState, + GpuChannel channel) + { + channel.TextureManager.UpdateRenderTargets(); + + var rtControl = state.RtControl; + var msaaMode = state.RtMsaaMode; + + pipeline.SamplesCount = msaaMode.SamplesInX() * msaaMode.SamplesInY(); + + int count = rtControl.UnpackCount(); + + for (int index = 0; index < Constants.TotalRenderTargets; index++) + { + int rtIndex = rtControl.UnpackPermutationIndex(index); + + var colorState = state.RtColorState[rtIndex]; + + if (index >= count || colorState.Format == 0 || colorState.WidthOrStride == 0) + { + pipeline.AttachmentEnable[index] = false; + pipeline.AttachmentFormats[index] = Format.R8G8B8A8Unorm; + } + else + { + pipeline.AttachmentEnable[index] = true; + pipeline.AttachmentFormats[index] = colorState.Format.Convert().Format; + } + } + + pipeline.DepthStencilEnable = state.RtDepthStencilEnable; + pipeline.DepthStencilFormat = pipeline.DepthStencilEnable ? state.RtDepthStencilState.Format.Convert().Format : Format.D24UnormS8Uint; + + pipeline.VertexBufferCount = Constants.TotalVertexBuffers; + pipeline.Topology = graphicsState.Topology; + } + + /// <summary> /// Gets a graphics shader program from the shader cache. /// This includes all the specified shader stages. /// </summary> @@ -237,6 +290,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This automatically translates, compiles and adds the code to the cache if not present. /// </remarks> /// <param name="state">GPU state</param> + /// <param name="pipeline">Pipeline state</param> /// <param name="channel">GPU channel</param> /// <param name="poolState">Texture pool state</param> /// <param name="graphicsState">3D engine state</param> @@ -244,6 +298,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <returns>Compiled graphics shader code</returns> public CachedShaderProgram GetGraphicsShader( ref ThreedClassState state, + ref ProgramPipelineState pipeline, GpuChannel channel, GpuChannelPoolState poolState, GpuChannelGraphicsState graphicsState, @@ -262,7 +317,9 @@ namespace Ryujinx.Graphics.Gpu.Shader TransformFeedbackDescriptor[] transformFeedbackDescriptors = GetTransformFeedbackDescriptors(ref state); - ShaderSpecializationState specState = new ShaderSpecializationState(graphicsState, transformFeedbackDescriptors); + UpdatePipelineInfo(ref state, ref pipeline, graphicsState, channel); + + ShaderSpecializationState specState = new ShaderSpecializationState(ref graphicsState, ref pipeline, transformFeedbackDescriptors); GpuAccessorState gpuAccessorState = new GpuAccessorState(poolState, default, graphicsState, specState, transformFeedbackDescriptors); ReadOnlySpan<ulong> addressesSpan = addresses.AsSpan(); @@ -270,6 +327,8 @@ namespace Ryujinx.Graphics.Gpu.Shader TranslatorContext[] translatorContexts = new TranslatorContext[Constants.ShaderStages + 1]; TranslatorContext nextStage = null; + TargetApi api = _context.Capabilities.Api; + for (int stageIndex = Constants.ShaderStages - 1; stageIndex >= 0; stageIndex--) { ulong gpuVa = addressesSpan[stageIndex + 1]; @@ -277,7 +336,7 @@ namespace Ryujinx.Graphics.Gpu.Shader if (gpuVa != 0) { GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState, stageIndex); - TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, DefaultFlags, gpuVa); + TranslatorContext currentStage = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags, gpuVa); if (nextStage != null) { @@ -286,7 +345,7 @@ namespace Ryujinx.Graphics.Gpu.Shader if (stageIndex == 0 && addresses.VertexA != 0) { - translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); + translatorContexts[0] = DecodeGraphicsShader(gpuAccessor, api, DefaultFlags | TranslationFlags.VertexA, addresses.VertexA); } translatorContexts[stageIndex + 1] = currentStage; @@ -336,13 +395,15 @@ namespace Ryujinx.Graphics.Gpu.Shader } } + ShaderSource[] shaderSourcesArray = shaderSources.ToArray(); + int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1; - IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources.ToArray(), new ShaderInfo(fragmentOutputMap)); + IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline)); gpShaders = new CachedShaderProgram(hostProgram, specState, shaders); _graphicsShaderCache.Add(gpShaders); - EnqueueProgramToSave(new ProgramToSave(gpShaders, hostProgram)); + EnqueueProgramToSave(gpShaders, hostProgram, shaderSourcesArray); _gpPrograms[addresses] = gpShaders; return gpShaders; @@ -355,7 +416,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <returns>Shader source</returns> public static ShaderSource CreateShaderSource(ShaderProgram program) { - return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language); + return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language); } /// <summary> @@ -364,11 +425,16 @@ namespace Ryujinx.Graphics.Gpu.Shader /// <remarks> /// This will not do anything if disk shader cache is disabled. /// </remarks> - /// <param name="programToSave">Program to be saved on disk</param> - private void EnqueueProgramToSave(ProgramToSave programToSave) + /// <param name="program">Cached shader program</param> + /// <param name="hostProgram">Host program</param> + /// <param name="sources">Source for each shader stage</param> + private void EnqueueProgramToSave(CachedShaderProgram program, IProgram hostProgram, ShaderSource[] sources) { if (_diskCacheHostStorage.CacheEnabled) { + byte[] binaryCode = _context.Capabilities.Api == TargetApi.Vulkan ? ShaderBinarySerializer.Pack(sources) : null; + ProgramToSave programToSave = new ProgramToSave(program, hostProgram, binaryCode); + _programsToSaveQueue.Enqueue(programToSave); } } @@ -480,11 +546,12 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Decode the binary Maxwell shader code to a translator context. /// </summary> /// <param name="gpuAccessor">GPU state accessor</param> + /// <param name="api">Graphics API that will be used with the shader</param> /// <param name="gpuVa">GPU virtual address of the binary shader code</param> /// <returns>The generated translator context</returns> - public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, ulong gpuVa) + public static TranslatorContext DecodeComputeShader(IGpuAccessor gpuAccessor, TargetApi api, ulong gpuVa) { - var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, DefaultFlags | TranslationFlags.Compute); + var options = CreateTranslationOptions(api, DefaultFlags | TranslationFlags.Compute); return Translator.CreateContext(gpuVa, gpuAccessor, options); } @@ -495,12 +562,13 @@ namespace Ryujinx.Graphics.Gpu.Shader /// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader. /// </remarks> /// <param name="gpuAccessor">GPU state accessor</param> + /// <param name="api">Graphics API that will be used with the shader</param> /// <param name="flags">Flags that controls shader translation</param> /// <param name="gpuVa">GPU virtual address of the shader code</param> /// <returns>The generated translator context</returns> - public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TranslationFlags flags, ulong gpuVa) + public static TranslatorContext DecodeGraphicsShader(IGpuAccessor gpuAccessor, TargetApi api, TranslationFlags flags, ulong gpuVa) { - var options = new TranslationOptions(TargetLanguage.Glsl, TargetApi.OpenGL, flags); + var options = CreateTranslationOptions(api, flags); return Translator.CreateContext(gpuVa, gpuAccessor, options); } @@ -596,6 +664,41 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Gets information about the bindings used by a shader program. + /// </summary> + /// <param name="info">Shader program information to get the information from</param> + /// <returns>Shader bindings</returns> + public static ShaderBindings GetBindings(ShaderProgramInfo info) + { + var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray(); + var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray(); + var textureBindings = info.Textures.Select(x => x.Binding).ToArray(); + var imageBindings = info.Images.Select(x => x.Binding).ToArray(); + + return new ShaderBindings( + uniformBufferBindings, + storageBufferBindings, + textureBindings, + imageBindings); + } + + /// <summary> + /// Creates shader translation options with the requested graphics API and flags. + /// The shader language is choosen based on the current configuration and graphics API. + /// </summary> + /// <param name="api">Target graphics API</param> + /// <param name="flags">Translation flags</param> + /// <returns>Translation options</returns> + private static TranslationOptions CreateTranslationOptions(TargetApi api, TranslationFlags flags) + { + TargetLanguage lang = GraphicsConfig.EnableSpirvCompilationOnVulkan && api == TargetApi.Vulkan + ? TargetLanguage.Spirv + : TargetLanguage.Glsl; + + return new TranslationOptions(lang, api, flags); + } + + /// <summary> /// Disposes the shader cache, deleting all the cached shaders. /// It's an error to use the shader cache after disposal. /// </summary> diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs index 7e39c8a3..c927f33d 100644 --- a/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs +++ b/Ryujinx.Graphics.Gpu/Shader/ShaderSpecializationState.cs @@ -1,6 +1,7 @@ using Ryujinx.Common.Memory; using Ryujinx.Graphics.Gpu.Image; using Ryujinx.Graphics.Gpu.Memory; +using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Gpu.Shader.DiskCache; using Ryujinx.Graphics.Shader; using System; @@ -19,6 +20,7 @@ namespace Ryujinx.Graphics.Gpu.Shader private const uint TfbdMagic = (byte)'T' | ((byte)'F' << 8) | ((byte)'B' << 16) | ((byte)'D' << 24); private const uint TexkMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'K' << 24); private const uint TexsMagic = (byte)'T' | ((byte)'E' << 8) | ((byte)'X' << 16) | ((byte)'S' << 24); + private const uint PgpsMagic = (byte)'P' | ((byte)'G' << 8) | ((byte)'P' << 16) | ((byte)'S' << 24); /// <summary> /// Flags indicating GPU state that is used by the shader. @@ -52,6 +54,11 @@ namespace Ryujinx.Graphics.Gpu.Shader public Array5<uint> ConstantBufferUse; /// <summary> + /// Pipeline state captured at the time of shader use. + /// </summary> + public ProgramPipelineState? PipelineState; + + /// <summary> /// Transform feedback buffers active at the time the shader was compiled. /// </summary> public TransformFeedbackDescriptor[] TransformFeedbackDescriptors; @@ -179,7 +186,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// Creates a new instance of the shader specialization state. /// </summary> /// <param name="state">Current compute engine state</param> - public ShaderSpecializationState(GpuChannelComputeState state) : this() + public ShaderSpecializationState(ref GpuChannelComputeState state) : this() { ComputeState = state; _compute = true; @@ -190,7 +197,7 @@ namespace Ryujinx.Graphics.Gpu.Shader /// </summary> /// <param name="state">Current 3D engine state</param> /// <param name="descriptors">Optional transform feedback buffers in use, if any</param> - public ShaderSpecializationState(GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this() + private ShaderSpecializationState(ref GpuChannelGraphicsState state, TransformFeedbackDescriptor[] descriptors) : this() { GraphicsState = state; _compute = false; @@ -245,6 +252,34 @@ namespace Ryujinx.Graphics.Gpu.Shader } /// <summary> + /// Creates a new instance of the shader specialization state. + /// </summary> + /// <param name="state">Current 3D engine state</param> + /// <param name="pipelineState">Current program pipeline state</param> + /// <param name="descriptors">Optional transform feedback buffers in use, if any</param> + public ShaderSpecializationState( + ref GpuChannelGraphicsState state, + ref ProgramPipelineState pipelineState, + TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors) + { + PipelineState = pipelineState; + } + + /// <summary> + /// Creates a new instance of the shader specialization state. + /// </summary> + /// <param name="state">Current 3D engine state</param> + /// <param name="pipelineState">Current program pipeline state</param> + /// <param name="descriptors">Optional transform feedback buffers in use, if any</param> + public ShaderSpecializationState( + ref GpuChannelGraphicsState state, + ProgramPipelineState? pipelineState, + TransformFeedbackDescriptor[] descriptors) : this(ref state, descriptors) + { + PipelineState = pipelineState; + } + + /// <summary> /// Indicates that the shader accesses the early Z force state. /// </summary> public void RecordEarlyZForce() @@ -463,6 +498,28 @@ namespace Ryujinx.Graphics.Gpu.Shader return false; } + if (graphicsState.DepthMode != GraphicsState.DepthMode) + { + return false; + } + + if (graphicsState.AlphaTestEnable != GraphicsState.AlphaTestEnable) + { + return false; + } + + if (graphicsState.AlphaTestEnable && + (graphicsState.AlphaTestCompare != GraphicsState.AlphaTestCompare || + graphicsState.AlphaTestReference != GraphicsState.AlphaTestReference)) + { + return false; + } + + if (!graphicsState.AttributeTypes.ToSpan().SequenceEqual(GraphicsState.AttributeTypes.ToSpan())) + { + return false; + } + return Matches(channel, poolState, checkTextures, isCompute: false); } @@ -685,6 +742,17 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } + bool hasPipelineState = false; + + dataReader.Read(ref hasPipelineState); + + if (hasPipelineState) + { + ProgramPipelineState pipelineState = default; + dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic); + specState.PipelineState = pipelineState; + } + if (specState._queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) { ushort tfCount = 0; @@ -743,6 +811,16 @@ namespace Ryujinx.Graphics.Gpu.Shader constantBufferUsePerStageMask &= ~(1 << index); } + bool hasPipelineState = PipelineState.HasValue; + + dataWriter.Write(ref hasPipelineState); + + if (hasPipelineState) + { + ProgramPipelineState pipelineState = PipelineState.Value; + dataWriter.WriteWithMagicAndSize(ref pipelineState, PgpsMagic); + } + if (_queriedState.HasFlag(QueriedStateFlags.TransformFeedback)) { ushort tfCount = (ushort)TransformFeedbackDescriptors.Length; |