diff options
Diffstat (limited to 'Ryujinx.Graphics.Gpu/Shader/DiskCache')
7 files changed, 342 insertions, 135 deletions
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 |