diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
index 7ea38bace2..bfb1cdc1b0 100644
--- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Configuration
///
/// The current version of the file format
///
- public const int CurrentVersion = 15;
+ public const int CurrentVersion = 16;
public int Version { get; set; }
@@ -128,6 +128,11 @@ namespace Ryujinx.Configuration
///
public bool EnableVsync { get; set; }
+ ///
+ /// Enables or disables Shader cache
+ ///
+ public bool EnableShaderCache { get; set; }
+
///
/// Enables or disables multi-core scheduling of threads
///
diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs
index d83d07d3c3..915cb77ebe 100644
--- a/Ryujinx.Common/Configuration/ConfigurationState.cs
+++ b/Ryujinx.Common/Configuration/ConfigurationState.cs
@@ -298,13 +298,19 @@ namespace Ryujinx.Configuration
///
public ReactiveObject EnableVsync { get; private set; }
+ ///
+ /// Enables or disables Shader cache
+ ///
+ public ReactiveObject EnableShaderCache { get; private set; }
+
public GraphicsSection()
{
- ResScale = new ReactiveObject();
- ResScaleCustom = new ReactiveObject();
- MaxAnisotropy = new ReactiveObject();
- ShadersDumpPath = new ReactiveObject();
- EnableVsync = new ReactiveObject();
+ ResScale = new ReactiveObject();
+ ResScaleCustom = new ReactiveObject();
+ MaxAnisotropy = new ReactiveObject();
+ ShadersDumpPath = new ReactiveObject();
+ EnableVsync = new ReactiveObject();
+ EnableShaderCache = new ReactiveObject();
}
}
@@ -401,6 +407,7 @@ namespace Ryujinx.Configuration
EnableDiscordIntegration = EnableDiscordIntegration,
CheckUpdatesOnStart = CheckUpdatesOnStart,
EnableVsync = Graphics.EnableVsync,
+ EnableShaderCache = Graphics.EnableShaderCache,
EnableMulticoreScheduling = System.EnableMulticoreScheduling,
EnablePtc = System.EnablePtc,
EnableFsIntegrityChecks = System.EnableFsIntegrityChecks,
@@ -461,6 +468,7 @@ namespace Ryujinx.Configuration
EnableDiscordIntegration.Value = true;
CheckUpdatesOnStart.Value = true;
Graphics.EnableVsync.Value = true;
+ Graphics.EnableShaderCache.Value = true;
System.EnableMulticoreScheduling.Value = true;
System.EnablePtc.Value = false;
System.EnableFsIntegrityChecks.Value = true;
@@ -727,6 +735,15 @@ namespace Ryujinx.Configuration
configurationFileUpdated = true;
}
+ if (configurationFileFormat.Version < 16)
+ {
+ Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 16.");
+
+ configurationFileFormat.EnableShaderCache = true;
+
+ configurationFileUpdated = true;
+ }
+
List inputConfig = new List();
inputConfig.AddRange(configurationFileFormat.ControllerConfig);
inputConfig.AddRange(configurationFileFormat.KeyboardConfig);
@@ -753,6 +770,7 @@ namespace Ryujinx.Configuration
EnableDiscordIntegration.Value = configurationFileFormat.EnableDiscordIntegration;
CheckUpdatesOnStart.Value = configurationFileFormat.CheckUpdatesOnStart;
Graphics.EnableVsync.Value = configurationFileFormat.EnableVsync;
+ Graphics.EnableShaderCache.Value = configurationFileFormat.EnableShaderCache;
System.EnableMulticoreScheduling.Value = configurationFileFormat.EnableMulticoreScheduling;
System.EnablePtc.Value = configurationFileFormat.EnablePtc;
System.EnableFsIntegrityChecks.Value = configurationFileFormat.EnableFsIntegrityChecks;
diff --git a/Ryujinx.Common/Hash128.cs b/Ryujinx.Common/Hash128.cs
new file mode 100644
index 0000000000..99cd015c44
--- /dev/null
+++ b/Ryujinx.Common/Hash128.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public struct Hash128 : IEquatable
+ {
+ public ulong Low;
+ public ulong High;
+
+ public override string ToString()
+ {
+ return $"{High:x16}{Low:x16}";
+ }
+
+ public static bool operator ==(Hash128 x, Hash128 y)
+ {
+ return x.Equals(y);
+ }
+
+ public static bool operator !=(Hash128 x, Hash128 y)
+ {
+ return !x.Equals(y);
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is Hash128 hash128 && Equals(hash128);
+ }
+
+ public bool Equals(Hash128 cmpObj)
+ {
+ return Low == cmpObj.Low && High == cmpObj.High;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Low, High);
+ }
+ }
+}
diff --git a/Ryujinx.Common/XXHash128.cs b/Ryujinx.Common/XXHash128.cs
new file mode 100644
index 0000000000..827e4cb221
--- /dev/null
+++ b/Ryujinx.Common/XXHash128.cs
@@ -0,0 +1,556 @@
+using System;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Common
+{
+ public static class XXHash128
+ {
+ private const int StripeLen = 64;
+ private const int AccNb = StripeLen / sizeof(ulong);
+ private const int SecretConsumeRate = 8;
+ private const int SecretLastAccStart = 7;
+ private const int SecretMergeAccsStart = 11;
+ private const int SecretSizeMin = 136;
+ private const int MidSizeStartOffset = 3;
+ private const int MidSizeLastOffset = 17;
+
+ private const uint Prime32_1 = 0x9E3779B1U;
+ private const uint Prime32_2 = 0x85EBCA77U;
+ private const uint Prime32_3 = 0xC2B2AE3DU;
+ private const uint Prime32_4 = 0x27D4EB2FU;
+ private const uint Prime32_5 = 0x165667B1U;
+
+ private const ulong Prime64_1 = 0x9E3779B185EBCA87UL;
+ private const ulong Prime64_2 = 0xC2B2AE3D27D4EB4FUL;
+ private const ulong Prime64_3 = 0x165667B19E3779F9UL;
+ private const ulong Prime64_4 = 0x85EBCA77C2B2AE63UL;
+ private const ulong Prime64_5 = 0x27D4EB2F165667C5UL;
+
+ private static readonly ulong[] Xxh3InitAcc = new ulong[]
+ {
+ Prime32_3,
+ Prime64_1,
+ Prime64_2,
+ Prime64_3,
+ Prime64_4,
+ Prime32_2,
+ Prime64_5,
+ Prime32_1
+ };
+
+ private static readonly byte[] Xxh3KSecret = new byte[]
+ {
+ 0xb8, 0xfe, 0x6c, 0x39, 0x23, 0xa4, 0x4b, 0xbe, 0x7c, 0x01, 0x81, 0x2c, 0xf7, 0x21, 0xad, 0x1c,
+ 0xde, 0xd4, 0x6d, 0xe9, 0x83, 0x90, 0x97, 0xdb, 0x72, 0x40, 0xa4, 0xa4, 0xb7, 0xb3, 0x67, 0x1f,
+ 0xcb, 0x79, 0xe6, 0x4e, 0xcc, 0xc0, 0xe5, 0x78, 0x82, 0x5a, 0xd0, 0x7d, 0xcc, 0xff, 0x72, 0x21,
+ 0xb8, 0x08, 0x46, 0x74, 0xf7, 0x43, 0x24, 0x8e, 0xe0, 0x35, 0x90, 0xe6, 0x81, 0x3a, 0x26, 0x4c,
+ 0x3c, 0x28, 0x52, 0xbb, 0x91, 0xc3, 0x00, 0xcb, 0x88, 0xd0, 0x65, 0x8b, 0x1b, 0x53, 0x2e, 0xa3,
+ 0x71, 0x64, 0x48, 0x97, 0xa2, 0x0d, 0xf9, 0x4e, 0x38, 0x19, 0xef, 0x46, 0xa9, 0xde, 0xac, 0xd8,
+ 0xa8, 0xfa, 0x76, 0x3f, 0xe3, 0x9c, 0x34, 0x3f, 0xf9, 0xdc, 0xbb, 0xc7, 0xc7, 0x0b, 0x4f, 0x1d,
+ 0x8a, 0x51, 0xe0, 0x4b, 0xcd, 0xb4, 0x59, 0x31, 0xc8, 0x9f, 0x7e, 0xc9, 0xd9, 0x78, 0x73, 0x64,
+ 0xea, 0xc5, 0xac, 0x83, 0x34, 0xd3, 0xeb, 0xc3, 0xc5, 0x81, 0xa0, 0xff, 0xfa, 0x13, 0x63, 0xeb,
+ 0x17, 0x0d, 0xdd, 0x51, 0xb7, 0xf0, 0xda, 0x49, 0xd3, 0x16, 0x55, 0x26, 0x29, 0xd4, 0x68, 0x9e,
+ 0x2b, 0x16, 0xbe, 0x58, 0x7d, 0x47, 0xa1, 0xfc, 0x8f, 0xf8, 0xb8, 0xd1, 0x7a, 0xd0, 0x31, 0xce,
+ 0x45, 0xcb, 0x3a, 0x8f, 0x95, 0x16, 0x04, 0x28, 0xaf, 0xd7, 0xfb, 0xca, 0xbb, 0x4b, 0x40, 0x7e
+ };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Mult32To64(ulong x, ulong y)
+ {
+ return (ulong)(uint)x * (ulong)(uint)y;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe static Hash128 Mult64To128(ulong lhs, ulong rhs)
+ {
+ // TODO: Use BigMul once .NET 5 lands.
+ if (Bmi2.X64.IsSupported)
+ {
+ ulong low;
+ ulong high = Bmi2.X64.MultiplyNoFlags(lhs, rhs, &low);
+ return new Hash128
+ {
+ Low = low,
+ High = high
+ };
+ }
+
+ ulong loLo = Mult32To64((uint)lhs, (uint)rhs);
+ ulong hiLo = Mult32To64(lhs >> 32, (uint)rhs);
+ ulong loHi = Mult32To64((uint)lhs, rhs >> 32);
+ ulong hiHi = Mult32To64(lhs >> 32, rhs >> 32);
+
+ ulong cross = (loLo >> 32) + (uint)hiLo + loHi;
+ ulong upper = (hiLo >> 32) + (cross >> 32) + hiHi;
+ ulong lower = (cross << 32) | (uint)loLo;
+
+ return new Hash128
+ {
+ Low = lower,
+ High = upper
+ };
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Mul128Fold64(ulong lhs, ulong rhs)
+ {
+ Hash128 product = Mult64To128(lhs, rhs);
+ return product.Low ^ product.High;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong XorShift64(ulong v64, int shift)
+ {
+ Debug.Assert(0 <= shift && shift < 64);
+ return v64 ^ (v64 >> shift);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Xxh3Avalanche(ulong h64)
+ {
+ h64 = XorShift64(h64, 37);
+ h64 *= 0x165667919E3779F9UL;
+ h64 = XorShift64(h64, 32);
+ return h64;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Xxh64Avalanche(ulong h64)
+ {
+ h64 ^= h64 >> 33;
+ h64 *= Prime64_2;
+ h64 ^= h64 >> 29;
+ h64 *= Prime64_3;
+ h64 ^= h64 >> 32;
+ return h64;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe static void Xxh3Accumulate512(Span acc, ReadOnlySpan input, ReadOnlySpan secret)
+ {
+ if (Avx2.IsSupported)
+ {
+ fixed (ulong* pAcc = acc)
+ {
+ fixed (byte* pInput = input, pSecret = secret)
+ {
+ Vector256* xAcc = (Vector256*)pAcc;
+ Vector256* xInput = (Vector256*)pInput;
+ Vector256* xSecret = (Vector256*)pSecret;
+
+ for (ulong i = 0; i < StripeLen / 32; i++)
+ {
+ Vector256 dataVec = xInput[i];
+ Vector256 keyVec = xSecret[i];
+ Vector256 dataKey = Avx2.Xor(dataVec, keyVec);
+ Vector256 dataKeyLo = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
+ Vector256 product = Avx2.Multiply(dataKey.AsUInt32(), dataKeyLo);
+ Vector256 dataSwap = Avx2.Shuffle(dataVec.AsUInt32(), 0b01001110);
+ Vector256 sum = Avx2.Add(xAcc[i], dataSwap.AsUInt64());
+ xAcc[i] = Avx2.Add(product, sum);
+ }
+ }
+ }
+ }
+ else if (Sse2.IsSupported)
+ {
+ fixed (ulong* pAcc = acc)
+ {
+ fixed (byte* pInput = input, pSecret = secret)
+ {
+ Vector128* xAcc = (Vector128*)pAcc;
+ Vector128* xInput = (Vector128*)pInput;
+ Vector128* xSecret = (Vector128*)pSecret;
+
+ for (ulong i = 0; i < StripeLen / 16; i++)
+ {
+ Vector128 dataVec = xInput[i];
+ Vector128 keyVec = xSecret[i];
+ Vector128 dataKey = Sse2.Xor(dataVec, keyVec);
+ Vector128 dataKeyLo = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
+ Vector128 product = Sse2.Multiply(dataKey.AsUInt32(), dataKeyLo);
+ Vector128 dataSwap = Sse2.Shuffle(dataVec.AsUInt32(), 0b01001110);
+ Vector128 sum = Sse2.Add(xAcc[i], dataSwap.AsUInt64());
+ xAcc[i] = Sse2.Add(product, sum);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < AccNb; i++)
+ {
+ ulong dataVal = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(i * sizeof(ulong)));
+ ulong dataKey = dataVal ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(i * sizeof(ulong)));
+ acc[i ^ 1] += dataVal;
+ acc[i] += Mult32To64((uint)dataKey, dataKey >> 32);
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private unsafe static void Xxh3ScrambleAcc(Span acc, ReadOnlySpan secret)
+ {
+ if (Avx2.IsSupported)
+ {
+ fixed (ulong* pAcc = acc)
+ {
+ fixed (byte* pSecret = secret)
+ {
+ Vector256 prime32 = Vector256.Create(Prime32_1);
+ Vector256* xAcc = (Vector256*)pAcc;
+ Vector256* xSecret = (Vector256*)pSecret;
+
+ for (ulong i = 0; i < StripeLen / 32; i++)
+ {
+ Vector256 accVec = xAcc[i];
+ Vector256 shifted = Avx2.ShiftRightLogical(accVec, 47);
+ Vector256 dataVec = Avx2.Xor(accVec, shifted);
+
+ Vector256 keyVec = xSecret[i];
+ Vector256 dataKey = Avx2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
+
+ Vector256 dataKeyHi = Avx2.Shuffle(dataKey.AsUInt32(), 0b00110001);
+ Vector256 prodLo = Avx2.Multiply(dataKey, prime32);
+ Vector256 prodHi = Avx2.Multiply(dataKeyHi, prime32);
+
+ xAcc[i] = Avx2.Add(prodLo, Avx2.ShiftLeftLogical(prodHi, 32));
+ }
+ }
+ }
+ }
+ else if (Sse2.IsSupported)
+ {
+ fixed (ulong* pAcc = acc)
+ {
+ fixed (byte* pSecret = secret)
+ {
+ Vector128 prime32 = Vector128.Create(Prime32_1);
+ Vector128* xAcc = (Vector128*)pAcc;
+ Vector128* xSecret = (Vector128*)pSecret;
+
+ for (ulong i = 0; i < StripeLen / 16; i++)
+ {
+ Vector128 accVec = xAcc[i];
+ Vector128 shifted = Sse2.ShiftRightLogical(accVec, 47);
+ Vector128 dataVec = Sse2.Xor(accVec, shifted);
+
+ Vector128 keyVec = xSecret[i];
+ Vector128 dataKey = Sse2.Xor(dataVec.AsUInt32(), keyVec.AsUInt32());
+
+ Vector128 dataKeyHi = Sse2.Shuffle(dataKey.AsUInt32(), 0b00110001);
+ Vector128 prodLo = Sse2.Multiply(dataKey, prime32);
+ Vector128 prodHi = Sse2.Multiply(dataKeyHi, prime32);
+
+ xAcc[i] = Sse2.Add(prodLo, Sse2.ShiftLeftLogical(prodHi, 32));
+ }
+ }
+ }
+ }
+ else
+ {
+ for (int i = 0; i < AccNb; i++)
+ {
+ ulong key64 = BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(i * sizeof(ulong)));
+ ulong acc64 = acc[i];
+ acc64 = XorShift64(acc64, 47);
+ acc64 ^= key64;
+ acc64 *= Prime32_1;
+ acc[i] = acc64;
+ }
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static void Xxh3Accumulate(Span acc, ReadOnlySpan input, ReadOnlySpan secret, int nbStripes)
+ {
+ for (int n = 0; n < nbStripes; n++)
+ {
+ ReadOnlySpan inData = input.Slice(n * StripeLen);
+ Xxh3Accumulate512(acc, inData, secret.Slice(n * SecretConsumeRate));
+ }
+ }
+
+ private static void Xxh3HashLongInternalLoop(Span acc, ReadOnlySpan input, ReadOnlySpan secret)
+ {
+ int nbStripesPerBlock = (secret.Length - StripeLen) / SecretConsumeRate;
+ int blockLen = StripeLen * nbStripesPerBlock;
+ int nbBlocks = (input.Length - 1) / blockLen;
+
+ Debug.Assert(secret.Length >= SecretSizeMin);
+
+ for (int n = 0; n < nbBlocks; n++)
+ {
+ Xxh3Accumulate(acc, input.Slice(n * blockLen), secret, nbStripesPerBlock);
+ Xxh3ScrambleAcc(acc, secret.Slice(secret.Length - StripeLen));
+ }
+
+ Debug.Assert(input.Length > StripeLen);
+
+ int nbStripes = (input.Length - 1 - (blockLen * nbBlocks)) / StripeLen;
+ Debug.Assert(nbStripes <= (secret.Length / SecretConsumeRate));
+ Xxh3Accumulate(acc, input.Slice(nbBlocks * blockLen), secret, nbStripes);
+
+ ReadOnlySpan p = input.Slice(input.Length - StripeLen);
+ Xxh3Accumulate512(acc, p, secret.Slice(secret.Length - StripeLen - SecretLastAccStart));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Xxh3Mix2Accs(Span acc, ReadOnlySpan secret)
+ {
+ return Mul128Fold64(
+ acc[0] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret),
+ acc[1] ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(8)));
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private static ulong Xxh3MergeAccs(Span acc, ReadOnlySpan secret, ulong start)
+ {
+ ulong result64 = start;
+
+ for (int i = 0; i < 4; i++)
+ {
+ result64 += Xxh3Mix2Accs(acc.Slice(2 * i), secret.Slice(16 * i));
+ }
+
+ return Xxh3Avalanche(result64);
+ }
+
+ private static Hash128 Xxh3HashLong128bInternal(ReadOnlySpan input, ReadOnlySpan secret)
+ {
+ Span acc = stackalloc ulong[AccNb]; // TODO: Use SkipLocalsInit attribute once .NET 5 lands.
+ Xxh3InitAcc.CopyTo(acc);
+
+ Xxh3HashLongInternalLoop(acc, input, secret);
+
+ Debug.Assert(acc.Length == 8);
+ Debug.Assert(secret.Length >= acc.Length * sizeof(ulong) + SecretMergeAccsStart);
+
+ return new Hash128
+ {
+ Low = Xxh3MergeAccs(acc, secret.Slice(SecretMergeAccsStart), (ulong)input.Length * Prime64_1),
+ High = Xxh3MergeAccs(
+ acc,
+ secret.Slice(secret.Length - acc.Length * sizeof(ulong) - SecretMergeAccsStart),
+ ~((ulong)input.Length * Prime64_2))
+ };
+ }
+
+ private static Hash128 Xxh3Len1To3128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(1 <= input.Length && input.Length <= 3);
+
+ byte c1 = input[0];
+ byte c2 = input[input.Length >> 1];
+ byte c3 = input[^1];
+
+ uint combinedL = ((uint)c1 << 16) | ((uint)c2 << 24) | c3 | ((uint)input.Length << 8);
+ uint combinedH = BitOperations.RotateLeft(BinaryPrimitives.ReverseEndianness(combinedL), 13);
+ ulong bitFlipL = (BinaryPrimitives.ReadUInt32LittleEndian(secret) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret.Slice(4))) + seed;
+ ulong bitFlipH = (BinaryPrimitives.ReadUInt32LittleEndian(secret.Slice(8)) ^ BinaryPrimitives.ReadUInt32LittleEndian(secret.Slice(12))) - seed;
+ ulong keyedLo = combinedL ^ bitFlipL;
+ ulong keyedHi = combinedH ^ bitFlipH;
+
+ return new Hash128
+ {
+ Low = Xxh64Avalanche(keyedLo),
+ High = Xxh64Avalanche(keyedHi)
+ };
+ }
+
+ private static Hash128 Xxh3Len4To8128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(4 <= input.Length && input.Length <= 8);
+
+ seed ^= BinaryPrimitives.ReverseEndianness((uint)seed) << 32;
+
+ uint inputLo = BinaryPrimitives.ReadUInt32LittleEndian(input);
+ uint inputHi = BinaryPrimitives.ReadUInt32LittleEndian(input.Slice(input.Length - 4));
+ ulong input64 = inputLo + ((ulong)inputHi << 32);
+ ulong bitFlip = (BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(16)) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(24))) + seed;
+ ulong keyed = input64 ^ bitFlip;
+
+ Hash128 m128 = Mult64To128(keyed, Prime64_1 + ((ulong)input.Length << 2));
+
+ m128.High += m128.Low << 1;
+ m128.Low ^= m128.High >> 3;
+
+ m128.Low = XorShift64(m128.Low, 35);
+ m128.Low *= 0x9FB21C651E98DF25UL;
+ m128.Low = XorShift64(m128.Low, 28);
+ m128.High = Xxh3Avalanche(m128.High);
+ return m128;
+ }
+
+ private static Hash128 Xxh3Len9To16128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(9 <= input.Length && input.Length <= 16);
+
+ ulong bitFlipL = (BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(32)) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(40))) - seed;
+ ulong bitFlipH = (BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(48)) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(56))) + seed;
+ ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
+ ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(input.Length - 8));
+
+ Hash128 m128 = Mult64To128(inputLo ^ inputHi ^ bitFlipL, Prime64_1);
+ m128.Low += ((ulong)input.Length - 1) << 54;
+ inputHi ^= bitFlipH;
+ m128.High += inputHi + Mult32To64((uint)inputHi, Prime32_2 - 1);
+ m128.Low ^= BinaryPrimitives.ReverseEndianness(m128.High);
+
+ Hash128 h128 = Mult64To128(m128.Low, Prime64_2);
+ h128.High += m128.High * Prime64_2;
+ h128.Low = Xxh3Avalanche(h128.Low);
+ h128.High = Xxh3Avalanche(h128.High);
+ return h128;
+ }
+
+ private static Hash128 Xxh3Len0To16128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(input.Length <= 16);
+
+ if (input.Length > 8)
+ {
+ return Xxh3Len9To16128b(input, secret, seed);
+ }
+ else if (input.Length >= 4)
+ {
+ return Xxh3Len4To8128b(input, secret, seed);
+ }
+ else if (input.Length != 0)
+ {
+ return Xxh3Len1To3128b(input, secret, seed);
+ }
+ else
+ {
+ Hash128 h128 = new Hash128();
+ ulong bitFlipL = BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(64)) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(72));
+ ulong bitFlipH = BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(80)) ^ BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(88));
+ h128.Low = Xxh64Avalanche(seed ^ bitFlipL);
+ h128.High = Xxh64Avalanche(seed ^ bitFlipH);
+ return h128;
+ }
+ }
+
+ private static ulong Xxh3Mix16b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ ulong inputLo = BinaryPrimitives.ReadUInt64LittleEndian(input);
+ ulong inputHi = BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(8));
+ return Mul128Fold64(
+ inputLo ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret) + seed),
+ inputHi ^ (BinaryPrimitives.ReadUInt64LittleEndian(secret.Slice(8)) - seed));
+ }
+
+ private static Hash128 Xxh128Mix32b(Hash128 acc, ReadOnlySpan input, ReadOnlySpan input2, ReadOnlySpan secret, ulong seed)
+ {
+ acc.Low += Xxh3Mix16b(input, secret, seed);
+ acc.Low ^= BinaryPrimitives.ReadUInt64LittleEndian(input2) + BinaryPrimitives.ReadUInt64LittleEndian(input2.Slice(8));
+ acc.High += Xxh3Mix16b(input2, secret.Slice(16), seed);
+ acc.High ^= BinaryPrimitives.ReadUInt64LittleEndian(input) + BinaryPrimitives.ReadUInt64LittleEndian(input.Slice(8));
+ return acc;
+ }
+
+ private static Hash128 Xxh3Len17To128128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(secret.Length >= SecretSizeMin);
+ Debug.Assert(16 < input.Length && input.Length <= 128);
+
+ Hash128 acc = new Hash128
+ {
+ Low = (ulong)input.Length * Prime64_1,
+ High = 0
+ };
+
+ if (input.Length > 32)
+ {
+ if (input.Length > 64)
+ {
+ if (input.Length > 96)
+ {
+ acc = Xxh128Mix32b(acc, input.Slice(48), input.Slice(input.Length - 64), secret.Slice(96), seed);
+ }
+ acc = Xxh128Mix32b(acc, input.Slice(32), input.Slice(input.Length - 48), secret.Slice(64), seed);
+ }
+ acc = Xxh128Mix32b(acc, input.Slice(16), input.Slice(input.Length - 32), secret.Slice(32), seed);
+ }
+ acc = Xxh128Mix32b(acc, input, input.Slice(input.Length - 16), secret, seed);
+
+ Hash128 h128 = new Hash128
+ {
+ Low = acc.Low + acc.High,
+ High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2
+ };
+ h128.Low = Xxh3Avalanche(h128.Low);
+ h128.High = 0UL - Xxh3Avalanche(h128.High);
+ return h128;
+ }
+
+ private static Hash128 Xxh3Len129To240128b(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(secret.Length >= SecretSizeMin);
+ Debug.Assert(128 < input.Length && input.Length <= 240);
+
+ Hash128 acc = new Hash128();
+
+ int nbRounds = input.Length / 32;
+ acc.Low = (ulong)input.Length * Prime64_1;
+ acc.High = 0;
+
+ for (int i = 0; i < 4; i++)
+ {
+ acc = Xxh128Mix32b(acc, input.Slice(32 * i), input.Slice(32 * i + 16), secret.Slice(32 * i), seed);
+ }
+
+ acc.Low = Xxh3Avalanche(acc.Low);
+ acc.High = Xxh3Avalanche(acc.High);
+ Debug.Assert(nbRounds >= 4);
+
+ for (int i = 4; i < nbRounds; i++)
+ {
+ acc = Xxh128Mix32b(acc, input.Slice(32 * i), input.Slice(32 * i + 16), secret.Slice(MidSizeStartOffset + 32 * (i - 4)), seed);
+ }
+
+ acc = Xxh128Mix32b(acc, input.Slice(input.Length - 16), input.Slice(input.Length - 32), secret.Slice(SecretSizeMin - MidSizeLastOffset - 16), 0UL - seed);
+
+ Hash128 h128 = new Hash128
+ {
+ Low = acc.Low + acc.High,
+ High = acc.Low * Prime64_1 + acc.High * Prime64_4 + ((ulong)input.Length - seed) * Prime64_2
+ };
+ h128.Low = Xxh3Avalanche(h128.Low);
+ h128.High = 0UL - Xxh3Avalanche(h128.High);
+ return h128;
+ }
+
+ private static Hash128 Xxh3128bitsInternal(ReadOnlySpan input, ReadOnlySpan secret, ulong seed)
+ {
+ Debug.Assert(secret.Length >= SecretSizeMin);
+
+ if (input.Length <= 16)
+ {
+ return Xxh3Len0To16128b(input, secret, seed);
+ }
+ else if (input.Length <= 128)
+ {
+ return Xxh3Len17To128128b(input, secret, seed);
+ }
+ else if (input.Length <= 240)
+ {
+ return Xxh3Len129To240128b(input, secret, seed);
+ }
+ else
+ {
+ return Xxh3HashLong128bInternal(input, secret);
+ }
+ }
+
+ public static Hash128 ComputeHash(ReadOnlySpan input)
+ {
+ return Xxh3128bitsInternal(input, Xxh3KSecret, 0UL);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.GAL/IProgram.cs b/Ryujinx.Graphics.GAL/IProgram.cs
index ef44fc47eb..5ab8346f2a 100644
--- a/Ryujinx.Graphics.GAL/IProgram.cs
+++ b/Ryujinx.Graphics.GAL/IProgram.cs
@@ -2,5 +2,8 @@ using System;
namespace Ryujinx.Graphics.GAL
{
- public interface IProgram : IDisposable { }
+ public interface IProgram : IDisposable
+ {
+ byte[] GetBinary();
+ }
}
diff --git a/Ryujinx.Graphics.GAL/IRenderer.cs b/Ryujinx.Graphics.GAL/IRenderer.cs
index 35c2146f2c..465c880539 100644
--- a/Ryujinx.Graphics.GAL/IRenderer.cs
+++ b/Ryujinx.Graphics.GAL/IRenderer.cs
@@ -27,6 +27,8 @@ namespace Ryujinx.Graphics.GAL
Capabilities GetCapabilities();
+ IProgram LoadProgramBinary(byte[] programBinary);
+
void SetBufferData(BufferHandle buffer, int offset, ReadOnlySpan data);
void UpdateCounters();
diff --git a/Ryujinx.Graphics.Gpu/Engine/Compute.cs b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
index cd5002ca9b..fd3114a794 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Compute.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Compute.cs
@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
TextureManager.SetComputeTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
TextureManager.SetComputeTextureBufferIndex(state.Get(MethodOffset.TextureBufferIndex));
- ShaderProgramInfo info = cs.Shaders[0].Program.Info;
+ ShaderProgramInfo info = cs.Shaders[0].Info;
for (int index = 0; index < info.CBuffers.Count; index++)
{
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index cab125b502..f408561a26 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -1000,14 +1000,14 @@ namespace Ryujinx.Graphics.Gpu.Engine
ShaderBundle gs = ShaderCache.GetGraphicsShader(state, addresses);
- _vsUsesInstanceId = gs.Shaders[0]?.Program.Info.UsesInstanceId ?? false;
+ _vsUsesInstanceId = gs.Shaders[0]?.Info.UsesInstanceId ?? false;
int storageBufferBindingsCount = 0;
int uniformBufferBindingsCount = 0;
for (int stage = 0; stage < Constants.ShaderStages; stage++)
{
- ShaderProgramInfo info = gs.Shaders[stage]?.Program.Info;
+ ShaderProgramInfo info = gs.Shaders[stage]?.Info;
_currentProgramInfo[stage] = info;
diff --git a/Ryujinx.Graphics.Gpu/GpuContext.cs b/Ryujinx.Graphics.Gpu/GpuContext.cs
index bdbf77a6cf..b62b5f3a41 100644
--- a/Ryujinx.Graphics.Gpu/GpuContext.cs
+++ b/Ryujinx.Graphics.Gpu/GpuContext.cs
@@ -1,9 +1,11 @@
+using Ryujinx.Common.Configuration;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Gpu.Engine;
using Ryujinx.Graphics.Gpu.Engine.GPFifo;
using Ryujinx.Graphics.Gpu.Memory;
using Ryujinx.Graphics.Gpu.Synchronization;
using System;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu
{
@@ -12,6 +14,16 @@ namespace Ryujinx.Graphics.Gpu
///
public sealed class GpuContext : IDisposable
{
+ ///
+ /// Event signaled when the host emulation context is ready to be used by the gpu context.
+ ///
+ public ManualResetEvent HostInitalized { get; }
+
+ ///
+ /// Event signaled when the gpu context is ready to be used.
+ ///
+ public ManualResetEvent ReadyEvent { get; }
+
///
/// Host renderer.
///
@@ -79,6 +91,22 @@ namespace Ryujinx.Graphics.Gpu
Window = new Window(this);
_caps = new Lazy(Renderer.GetCapabilities);
+
+ HostInitalized = new ManualResetEvent(false);
+ ReadyEvent = new ManualResetEvent(false);
+ }
+
+ ///
+ /// Initialize the GPU emulation context.
+ ///
+ /// The log level required.
+ public void Initialize(GraphicsDebugLevel logLevel)
+ {
+ HostInitalized.WaitOne();
+
+ Renderer.Initialize(logLevel);
+ Methods.ShaderCache.Initialize();
+ ReadyEvent.Set();
}
///
@@ -113,6 +141,8 @@ namespace Ryujinx.Graphics.Gpu
Methods.TextureManager.Dispose();
Renderer.Dispose();
GPFifo.Dispose();
+ HostInitalized.Dispose();
+ ReadyEvent.Dispose();
}
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
index 4d16628f31..af980e772f 100644
--- a/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
+++ b/Ryujinx.Graphics.Gpu/GraphicsConfig.cs
@@ -32,5 +32,16 @@ namespace Ryujinx.Graphics.Gpu
/// Enables or disables the Just-in-Time compiler for GPU Macro code.
///
public static bool EnableMacroJit = true;
+
+ ///
+ /// Title id of the current running game.
+ /// Used by the shader cache.
+ ///
+ public static string TitleId;
+
+ ///
+ /// Enables or disables the shader cache.
+ ///
+ public static bool EnableShaderCache;
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
index 313b8e2052..74fb988740 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+
namespace Ryujinx.Graphics.Gpu.Image
{
///
@@ -227,5 +229,24 @@ namespace Ryujinx.Graphics.Gpu.Image
{
return (TextureMsaaMode)((Word7 >> 8) & 0xf);
}
+
+ ///
+ /// Create the equivalent of this TextureDescriptor for the shader cache.
+ ///
+ /// The equivalent of this TextureDescriptor for the shader cache.
+ public GuestTextureDescriptor ToCache()
+ {
+ GuestTextureDescriptor result = new GuestTextureDescriptor
+ {
+ Handle = uint.MaxValue,
+ Descriptor = this
+ };
+
+ // Clear the virtual address
+ result.Descriptor.Word0 = 0;
+ result.Descriptor.Word2 &= 0xFFFF0000;
+
+ return result;
+ }
}
}
diff --git a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
index a9e81be357..f40857b40a 100644
--- a/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
+++ b/Ryujinx.Graphics.Gpu/Ryujinx.Graphics.Gpu.csproj
@@ -1,4 +1,4 @@
-
+
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
new file mode 100644
index 0000000000..effd893a23
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheCollection.cs
@@ -0,0 +1,595 @@
+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.IO.Compression;
+using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache
+{
+ ///
+ /// Represent a cache collection handling one shader cache.
+ ///
+ class CacheCollection : IDisposable
+ {
+ ///
+ /// Possible operation to do on the .
+ ///
+ private enum CacheFileOperation
+ {
+ ///
+ /// Save a new entry in the temp cache.
+ ///
+ SaveTempEntry,
+
+ ///
+ /// Save the hash manifest.
+ ///
+ SaveManifest,
+
+ ///
+ /// Flush temporary cache to archive.
+ ///
+ FlushToArchive,
+
+ ///
+ /// Signal when hitting this point. This is useful to know if all previous operations were performed.
+ ///
+ Synchronize
+ }
+
+ ///
+ /// Represent an operation to perform on the .
+ ///
+ private class CacheFileOperationTask
+ {
+ ///
+ /// The type of operation to perform.
+ ///
+ public CacheFileOperation Type;
+
+ ///
+ /// The data associated to this operation or null.
+ ///
+ public object Data;
+ }
+
+ ///
+ /// Data associated to the operation.
+ ///
+ private class CacheFileSaveEntryTaskData
+ {
+ ///
+ /// The key of the entry to cache.
+ ///
+ public Hash128 Key;
+
+ ///
+ /// The value of the entry to cache.
+ ///
+ public byte[] Value;
+ }
+
+ ///
+ /// The directory of the shader cache.
+ ///
+ private readonly string _cacheDirectory;
+
+ ///
+ /// The version of the cache.
+ ///
+ private readonly ulong _version;
+
+ ///
+ /// The hash type of the cache.
+ ///
+ private readonly CacheHashType _hashType;
+
+ ///
+ /// The graphics API of the cache.
+ ///
+ private readonly CacheGraphicsApi _graphicsApi;
+
+ ///
+ /// The table of all the hash registered in the cache.
+ ///
+ private HashSet _hashTable;
+
+ ///
+ /// The queue of operations to be performed by the file writer worker.
+ ///
+ private AsyncWorkQueue _fileWriterWorkerQueue;
+
+ ///
+ /// Main storage of the cache collection.
+ ///
+ private ZipArchive _cacheArchive;
+
+ ///
+ /// Immutable copy of the hash table.
+ ///
+ public ReadOnlySpan HashTable => _hashTable.ToArray();
+
+ ///
+ /// Get the temp path to the cache data directory.
+ ///
+ /// The temp path to the cache data directory
+ private string GetCacheTempDataPath() => Path.Combine(_cacheDirectory, "temp");
+
+ ///
+ /// The path to the cache archive file.
+ ///
+ /// The path to the cache archive file
+ private string GetArchivePath() => Path.Combine(_cacheDirectory, "cache.zip");
+
+ ///
+ /// The path to the cache manifest file.
+ ///
+ /// The path to the cache manifest file
+ private string GetManifestPath() => Path.Combine(_cacheDirectory, "cache.info");
+
+ ///
+ /// Create a new temp path to the given cached file via its hash.
+ ///
+ /// The hash of the cached data
+ /// New path to the given cached file
+ private string GenCacheTempFilePath(Hash128 key) => Path.Combine(GetCacheTempDataPath(), key.ToString());
+
+ ///
+ /// Create a new cache collection.
+ ///
+ /// The directory of the shader cache
+ /// The hash type of the shader cache
+ /// The graphics api of the shader cache
+ /// The shader provider name of the shader cache
+ /// The name of the cache
+ /// The version of the cache
+ public CacheCollection(string baseCacheDirectory, CacheHashType hashType, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName, ulong version)
+ {
+ if (hashType != CacheHashType.XxHash128)
+ {
+ throw new NotImplementedException($"{hashType}");
+ }
+
+ _cacheDirectory = GenerateCachePath(baseCacheDirectory, graphicsApi, shaderProvider, cacheName);
+ _graphicsApi = graphicsApi;
+ _hashType = hashType;
+ _version = version;
+ _hashTable = new HashSet();
+
+ Load();
+
+ _fileWriterWorkerQueue = new AsyncWorkQueue(HandleCacheTask, $"CacheCollection.Worker.{cacheName}");
+ }
+
+ ///
+ /// Load the cache manifest file and recreate it if invalid.
+ ///
+ private void Load()
+ {
+ bool isInvalid = false;
+
+ if (!Directory.Exists(_cacheDirectory))
+ {
+ isInvalid = true;
+ }
+ else
+ {
+ string manifestPath = GetManifestPath();
+
+ if (File.Exists(manifestPath))
+ {
+ Memory rawManifest = File.ReadAllBytes(manifestPath);
+
+ if (MemoryMarshal.TryRead(rawManifest.Span, out CacheManifestHeader manifestHeader))
+ {
+ Memory hashTableRaw = rawManifest.Slice(Unsafe.SizeOf());
+
+ isInvalid = !manifestHeader.IsValid(_version, _graphicsApi, _hashType, hashTableRaw.Span);
+
+ if (!isInvalid)
+ {
+ ReadOnlySpan hashTable = MemoryMarshal.Cast(hashTableRaw.Span);
+
+ foreach (Hash128 hash in hashTable)
+ {
+ _hashTable.Add(hash);
+ }
+ }
+ }
+ }
+ else
+ {
+ isInvalid = true;
+ }
+ }
+
+ if (isInvalid)
+ {
+ 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();
+ }
+
+ ///
+ /// Remove given entries from the manifest.
+ ///
+ /// Entries to remove from the manifest
+ public void RemoveManifestEntries(HashSet entries)
+ {
+ lock (_hashTable)
+ {
+ foreach (Hash128 entry in entries)
+ {
+ _hashTable.Remove(entry);
+ }
+
+ SaveManifest();
+ }
+ }
+
+ ///
+ /// Queue a task to flush temporary files to the archive on the worker.
+ ///
+ public void FlushToArchiveAsync()
+ {
+ _fileWriterWorkerQueue.Add(new CacheFileOperationTask
+ {
+ Type = CacheFileOperation.FlushToArchive
+ });
+ }
+
+ ///
+ /// Wait for all tasks before this given point to be done.
+ ///
+ public void Synchronize()
+ {
+ using (ManualResetEvent evnt = new ManualResetEvent(false))
+ {
+ _fileWriterWorkerQueue.Add(new CacheFileOperationTask
+ {
+ Type = CacheFileOperation.Synchronize,
+ Data = evnt
+ });
+
+ evnt.WaitOne();
+ }
+ }
+
+ ///
+ /// Flush temporary files to the archive.
+ ///
+ /// This dispose if not null and reinstantiate it.
+ private void FlushToArchive()
+ {
+ EnsureArchiveUpToDate();
+
+ // Open the zip in readonly to avoid anyone modifying/corrupting it during normal operations.
+ _cacheArchive = ZipFile.Open(GetArchivePath(), ZipArchiveMode.Read);
+ }
+
+ ///
+ /// Save temporary files not in archive.
+ ///
+ /// This dispose if not null.
+ public void EnsureArchiveUpToDate()
+ {
+ // First close previous opened instance if found.
+ if (_cacheArchive != null)
+ {
+ _cacheArchive.Dispose();
+ }
+
+ string archivePath = GetArchivePath();
+
+ // Open the zip in read/write.
+ _cacheArchive = ZipFile.Open(archivePath, ZipArchiveMode.Update);
+
+ Logger.Info?.Print(LogClass.Gpu, $"Updating cache collection archive {archivePath}...");
+
+ // Update the content of the zip.
+ lock (_hashTable)
+ {
+ foreach (Hash128 hash in _hashTable)
+ {
+ string cacheTempFilePath = GenCacheTempFilePath(hash);
+
+ if (File.Exists(cacheTempFilePath))
+ {
+ string cacheHash = $"{hash}";
+
+ ZipArchiveEntry entry = _cacheArchive.GetEntry(cacheHash);
+
+ entry?.Delete();
+
+ _cacheArchive.CreateEntryFromFile(cacheTempFilePath, cacheHash);
+ File.Delete(cacheTempFilePath);
+ }
+ }
+
+ // Close the instance to force a flush.
+ _cacheArchive.Dispose();
+ _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}.");
+ }
+
+ ///
+ /// Save the manifest file.
+ ///
+ private void SaveManifest()
+ {
+ CacheManifestHeader manifestHeader = new CacheManifestHeader(_version, _graphicsApi, _hashType);
+
+ byte[] data;
+
+ lock (_hashTable)
+ {
+ data = new byte[Unsafe.SizeOf() + _hashTable.Count * Unsafe.SizeOf()];
+
+ // CacheManifestHeader has the same size as a Hash128.
+ Span dataSpan = MemoryMarshal.Cast(data.AsSpan()).Slice(1);
+
+ int i = 0;
+
+ foreach (Hash128 hash in _hashTable)
+ {
+ dataSpan[i++] = hash;
+ }
+ }
+
+ manifestHeader.UpdateChecksum(data.AsSpan().Slice(Unsafe.SizeOf()));
+
+ MemoryMarshal.Write(data, ref manifestHeader);
+
+ File.WriteAllBytes(GetManifestPath(), data);
+ }
+
+ ///
+ /// Generate the path to the cache directory.
+ ///
+ /// The base of the cache directory
+ /// The graphics api in use
+ /// The name of the shader provider in use
+ /// The name of the cache
+ /// The path to the cache directory
+ private static string GenerateCachePath(string baseCacheDirectory, CacheGraphicsApi graphicsApi, string shaderProvider, string cacheName)
+ {
+ string graphicsApiName = graphicsApi switch
+ {
+ CacheGraphicsApi.OpenGL => "opengl",
+ CacheGraphicsApi.OpenGLES => "opengles",
+ CacheGraphicsApi.Vulkan => "vulkan",
+ CacheGraphicsApi.DirectX => "directx",
+ CacheGraphicsApi.Metal => "metal",
+ CacheGraphicsApi.Guest => "guest",
+ _ => throw new NotImplementedException(graphicsApi.ToString()),
+ };
+
+ return Path.Combine(baseCacheDirectory, graphicsApiName, shaderProvider, cacheName);
+ }
+
+ ///
+ /// Get a cached file with the given hash.
+ ///
+ /// The given hash
+ /// The cached file if present or null
+ public byte[] GetValueRaw(ref Hash128 keyHash)
+ {
+ return GetValueRawFromArchive(ref keyHash) ?? GetValueRawFromFile(ref keyHash);
+ }
+
+ ///
+ /// Get a cached file with the given hash that is present in the archive.
+ ///
+ /// The given hash
+ /// The cached file if present or null
+ private byte[] GetValueRawFromArchive(ref Hash128 keyHash)
+ {
+ bool found;
+
+ lock (_hashTable)
+ {
+ found = _hashTable.Contains(keyHash);
+ }
+
+ if (found)
+ {
+ ZipArchiveEntry archiveEntry = _cacheArchive.GetEntry($"{keyHash}");
+
+ if (archiveEntry != null)
+ {
+ try
+ {
+ byte[] result = new byte[archiveEntry.Length];
+
+ using (Stream archiveStream = archiveEntry.Open())
+ {
+ archiveStream.Read(result);
+
+ return result;
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file {keyHash} from archive");
+ Logger.Error?.Print(LogClass.Gpu, e.ToString());
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Get a cached file with the given hash that is not present in the archive.
+ ///
+ /// The given hash
+ /// The cached file if present or null
+ private byte[] GetValueRawFromFile(ref Hash128 keyHash)
+ {
+ bool found;
+
+ lock (_hashTable)
+ {
+ found = _hashTable.Contains(keyHash);
+ }
+
+ if (found)
+ {
+ string cacheTempFilePath = GenCacheTempFilePath(keyHash);
+
+ try
+ {
+ return File.ReadAllBytes(GenCacheTempFilePath(keyHash));
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.Gpu, $"Cannot load cache file at {cacheTempFilePath}");
+ Logger.Error?.Print(LogClass.Gpu, e.ToString());
+ }
+ }
+
+ return null;
+ }
+
+ private void HandleCacheTask(CacheFileOperationTask task)
+ {
+ switch (task.Type)
+ {
+ case CacheFileOperation.SaveTempEntry:
+ SaveTempEntry((CacheFileSaveEntryTaskData)task.Data);
+ break;
+ case CacheFileOperation.SaveManifest:
+ SaveManifest();
+ break;
+ case CacheFileOperation.FlushToArchive:
+ FlushToArchive();
+ break;
+ case CacheFileOperation.Synchronize:
+ ((ManualResetEvent)task.Data).Set();
+ break;
+ default:
+ throw new NotImplementedException($"{task.Type}");
+ }
+
+ }
+
+ ///
+ /// Save a new entry in the temp cache.
+ ///
+ /// The entry to save in the temp cache
+ private void SaveTempEntry(CacheFileSaveEntryTaskData entry)
+ {
+ string tempPath = GenCacheTempFilePath(entry.Key);
+
+ File.WriteAllBytes(tempPath, entry.Value);
+ }
+
+ ///
+ /// Add a new value in the cache with a given hash.
+ ///
+ /// The hash to use for the value in the cache
+ /// The value to cache
+ public void AddValue(ref Hash128 keyHash, byte[] value)
+ {
+ Debug.Assert(value != null);
+ Debug.Assert(GetValueRaw(ref keyHash) != 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,
+ });
+ }
+
+ ///
+ /// Replace a value at the given hash in the cache.
+ ///
+ /// The hash to use for the value in the cache
+ /// The value to cache
+ public void ReplaceValue(ref Hash128 keyHash, byte[] value)
+ {
+ Debug.Assert(value != null);
+
+ // Only queue file change operations
+ _fileWriterWorkerQueue.Add(new CacheFileOperationTask
+ {
+ Type = CacheFileOperation.SaveTempEntry,
+ Data = new CacheFileSaveEntryTaskData
+ {
+ Key = keyHash,
+ Value = value
+ }
+ });
+ }
+
+ 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/CacheManager.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
new file mode 100644
index 0000000000..d241eb010a
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/CacheManager.cs
@@ -0,0 +1,168 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Configuration;
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache
+{
+ ///
+ /// Global Manager of the shader cache.
+ ///
+ class CacheManager : IDisposable
+ {
+ private CacheGraphicsApi _graphicsApi;
+ private CacheHashType _hashType;
+ private string _shaderProvider;
+
+ ///
+ /// Cache storing raw Maxwell shaders as programs.
+ ///
+ private CacheCollection _guestProgramCache;
+
+ ///
+ /// Cache storing raw host programs.
+ ///
+ private CacheCollection _hostProgramCache;
+
+ ///
+ /// Version of the guest cache shader (to increment when guest cache structure change).
+ ///
+ private const ulong GuestCacheVersion = 1;
+
+ ///
+ /// Create a new cache manager instance
+ ///
+ /// The graphics api in use
+ /// The hash type in use for the cache
+ /// The name of the codegen provider
+ /// The guest application title ID
+ /// Version of the codegen
+ public CacheManager(CacheGraphicsApi graphicsApi, CacheHashType hashType, string shaderProvider, string titleId, ulong shaderCodeGenVersion)
+ {
+ _graphicsApi = graphicsApi;
+ _hashType = hashType;
+ _shaderProvider = shaderProvider;
+
+ string baseCacheDirectory = Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
+
+ _guestProgramCache = new CacheCollection(baseCacheDirectory, _hashType, CacheGraphicsApi.Guest, "", "program", GuestCacheVersion);
+ _hostProgramCache = new CacheCollection(baseCacheDirectory, _hashType, _graphicsApi, _shaderProvider, "host", shaderCodeGenVersion);
+ }
+
+
+ ///
+ /// Entries to remove from the manifest.
+ ///
+ /// Entries to remove from the manifest of all caches
+ public void RemoveManifestEntries(HashSet entries)
+ {
+ _guestProgramCache.RemoveManifestEntries(entries);
+ _hostProgramCache.RemoveManifestEntries(entries);
+ }
+
+ ///
+ /// Queue a task to flush temporary files to the archives.
+ ///
+ public void FlushToArchive()
+ {
+ _guestProgramCache.FlushToArchiveAsync();
+ _hostProgramCache.FlushToArchiveAsync();
+ }
+
+ ///
+ /// Wait for all tasks before this given point to be done.
+ ///
+ public void Synchronize()
+ {
+ _guestProgramCache.Synchronize();
+ _hostProgramCache.Synchronize();
+ }
+
+ ///
+ /// Computes the hash of some data using the current cache hashing algorithm.
+ ///
+ /// Some data to generate a hash for.
+ /// The hash of some data using the current hashing algorithm of the cache
+ public Hash128 ComputeHash(ReadOnlySpan data)
+ {
+ return XXHash128.ComputeHash(data);
+ }
+
+ ///
+ /// Save a shader program not present in the program cache.
+ ///
+ /// Target program code hash
+ /// Guest program raw data
+ /// Host program raw data
+ public void SaveProgram(ref Hash128 programCodeHash, byte[] guestProgram, byte[] hostProgram)
+ {
+ _guestProgramCache.AddValue(ref programCodeHash, guestProgram);
+ _hostProgramCache.AddValue(ref programCodeHash, hostProgram);
+ }
+
+ ///
+ /// Add a host shader program not present in the program cache.
+ ///
+ /// Target program code hash
+ /// Host program raw data
+ public void AddHostProgram(ref Hash128 programCodeHash, byte[] data)
+ {
+ _hostProgramCache.AddValue(ref programCodeHash, data);
+ }
+
+ ///
+ /// Replace a host shader program present in the program cache.
+ ///
+ /// Target program code hash
+ /// Host program raw data
+ public void ReplaceHostProgram(ref Hash128 programCodeHash, byte[] data)
+ {
+ _hostProgramCache.ReplaceValue(ref programCodeHash, data);
+ }
+
+ ///
+ /// Get all guest program hashes.
+ ///
+ /// All guest program hashes
+ public ReadOnlySpan GetGuestProgramList()
+ {
+ return _guestProgramCache.HashTable;
+ }
+
+ ///
+ /// Get a host program by hash.
+ ///
+ /// The given hash
+ /// The host program if present or null
+ public byte[] GetHostProgramByHash(ref Hash128 hash)
+ {
+ return _hostProgramCache.GetValueRaw(ref hash);
+ }
+
+ ///
+ /// Get a guest program by hash.
+ ///
+ /// The given hash
+ /// The guest program if present or null
+ 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
new file mode 100644
index 0000000000..9f8b5c3979
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheGraphicsApi.cs
@@ -0,0 +1,38 @@
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Graphics API type accepted by the shader cache.
+ ///
+ enum CacheGraphicsApi : byte
+ {
+ ///
+ /// OpenGL Core
+ ///
+ OpenGL,
+
+ ///
+ /// OpenGL ES
+ ///
+ OpenGLES,
+
+ ///
+ /// Vulkan
+ ///
+ Vulkan,
+
+ ///
+ /// DirectX
+ ///
+ DirectX,
+
+ ///
+ /// Metal
+ ///
+ Metal,
+
+ ///
+ /// Guest, used to cache games raw shader programs.
+ ///
+ Guest
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs
new file mode 100644
index 0000000000..e4ebe416d6
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheHashType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Hash algorithm accepted by the shader cache.
+ ///
+ enum CacheHashType : byte
+ {
+ ///
+ /// xxHash128
+ ///
+ XxHash128
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
new file mode 100644
index 0000000000..3f198dca5d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/CacheManifestHeader.cs
@@ -0,0 +1,97 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Header of the shader cache manifest.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ struct CacheManifestHeader
+ {
+ ///
+ /// The version of the cache.
+ ///
+ public ulong Version;
+
+ ///
+ /// The graphics api used for this cache.
+ ///
+ public CacheGraphicsApi GraphicsApi;
+
+ ///
+ /// The hash type used for this cache.
+ ///
+ public CacheHashType HashType;
+
+ ///
+ /// CRC-16 checksum over the data in the file.
+ ///
+ public ushort TableChecksum;
+
+ ///
+ /// Construct a new cache manifest header.
+ ///
+ /// The version of the cache
+ /// The graphics api used for this cache
+ /// The hash type used for this cache
+ public CacheManifestHeader(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType)
+ {
+ Version = version;
+ GraphicsApi = graphicsApi;
+ HashType = hashType;
+ TableChecksum = 0;
+ }
+
+ ///
+ /// Update the checksum in the header.
+ ///
+ /// The data to perform the checksum on
+ public void UpdateChecksum(ReadOnlySpan data)
+ {
+ TableChecksum = CalculateCrc16(data);
+ }
+
+ ///
+ /// Calculate a CRC-16 over data.
+ ///
+ /// The data to perform the CRC-16 on
+ /// A CRC-16 over data
+ private static ushort CalculateCrc16(ReadOnlySpan 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;
+ }
+
+ ///
+ /// Check the validity of the header.
+ ///
+ /// The target version in use
+ /// The target graphics api in use
+ /// The target hash type in use
+ /// The data after this header
+ /// True if the header is valid
+ public bool IsValid(ulong version, CacheGraphicsApi graphicsApi, CacheHashType hashType, ReadOnlySpan data)
+ {
+ return Version == version && 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
new file mode 100644
index 0000000000..396b044309
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestGpuAccessorHeader.cs
@@ -0,0 +1,62 @@
+using Ryujinx.Graphics.Shader;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Header of a cached guest gpu accessor.
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
+ struct GuestGpuAccessorHeader
+ {
+ ///
+ /// The count of texture descriptors.
+ ///
+ public int TextureDescriptorCount;
+
+ ///
+ /// Local Size X for compute shaders.
+ ///
+ public int ComputeLocalSizeX;
+
+ ///
+ /// Local Size Y for compute shaders.
+ ///
+ public int ComputeLocalSizeY;
+
+ ///
+ /// Local Size Z for compute shaders.
+ ///
+ public int ComputeLocalSizeZ;
+
+ ///
+ /// Local Memory size in bytes for compute shaders.
+ ///
+ public int ComputeLocalMemorySize;
+
+ ///
+ /// Shared Memory size in bytes for compute shaders.
+ ///
+ public int ComputeSharedMemorySize;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public int Reserved1;
+
+ ///
+ /// Current primitive topology for geometry shaders.
+ ///
+ public InputTopology PrimitiveTopology;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public ushort Reserved2;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public byte Reserved3;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
new file mode 100644
index 0000000000..45a442e2bd
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntry.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Represent a cached shader entry in a guest shader program.
+ ///
+ class GuestShaderCacheEntry
+ {
+ ///
+ /// The header of the cached shader entry.
+ ///
+ public GuestShaderCacheEntryHeader Header { get; }
+
+ ///
+ /// The code of this shader.
+ ///
+ /// If a Vertex A is present, this also contains the code 2 section.
+ public byte[] Code { get; }
+
+ ///
+ /// The textures descriptors used for this shader.
+ ///
+ public Dictionary TextureDescriptors { get; }
+
+ ///
+ /// Create a new instance of .
+ ///
+ /// The header of the cached shader entry
+ /// The code of this shader
+ private GuestShaderCacheEntry(GuestShaderCacheEntryHeader header, byte[] code)
+ {
+ Header = header;
+ Code = code;
+ TextureDescriptors = new Dictionary();
+ }
+
+ ///
+ /// Parse a raw cached user shader program into an array of shader cache entry.
+ ///
+ /// The raw cached user shader program
+ /// The user shader program header
+ /// An array of shader cache entry
+ public static GuestShaderCacheEntry[] Parse(ref ReadOnlySpan data, out GuestShaderCacheHeader fileHeader)
+ {
+ fileHeader = MemoryMarshal.Read(data);
+
+ data = data.Slice(Unsafe.SizeOf());
+
+ ReadOnlySpan entryHeaders = MemoryMarshal.Cast(data.Slice(0, fileHeader.Count * Unsafe.SizeOf()));
+
+ data = data.Slice(fileHeader.Count * Unsafe.SizeOf());
+
+ 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 textureDescriptors = MemoryMarshal.Cast(data.Slice(0, header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf()));
+
+ foreach (GuestTextureDescriptor textureDescriptor in textureDescriptors)
+ {
+ result[i].TextureDescriptors.Add((int)textureDescriptor.Handle, textureDescriptor);
+ }
+
+ data = data.Slice(header.GpuAccessorHeader.TextureDescriptorCount * Unsafe.SizeOf());
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs
new file mode 100644
index 0000000000..6d5bb28dce
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheEntryHeader.cs
@@ -0,0 +1,67 @@
+using Ryujinx.Graphics.Shader;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// The header of a guest shader entry in a guest shader program.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x30)]
+ struct GuestShaderCacheEntryHeader
+ {
+ ///
+ /// The stage of this shader.
+ ///
+ public ShaderStage Stage;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public byte Reserved1;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public byte Reserved2;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public byte Reserved3;
+
+ ///
+ /// The size of the code section.
+ ///
+ public int Size;
+
+ ///
+ /// The size of the code2 section if present. (Vertex A)
+ ///
+ public int SizeA;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public int Reserved4;
+
+ ///
+ /// The header of the cached gpu accessor.
+ ///
+ public GuestGpuAccessorHeader GpuAccessorHeader;
+
+ ///
+ /// Create a new guest shader entry header.
+ ///
+ /// The stage of this shader
+ /// The size of the code section
+ /// The size of the code2 section if present (Vertex A)
+ /// The header of the cached gpu accessor
+ public GuestShaderCacheEntryHeader(ShaderStage stage, int size, int sizeA, GuestGpuAccessorHeader gpuAccessorHeader) : this()
+ {
+ Stage = stage;
+ Size = size;
+ SizeA = sizeA;
+ GpuAccessorHeader = gpuAccessorHeader;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs
new file mode 100644
index 0000000000..700be47d1d
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheHeader.cs
@@ -0,0 +1,42 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// The header of a shader program in the guest cache.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
+ struct GuestShaderCacheHeader
+ {
+ ///
+ /// The count of shaders defining this program.
+ ///
+ public byte Count;
+
+ ///
+ /// The count of transform feedback data used in this program.
+ ///
+ public byte TransformFeedbackCount;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public ushort Reserved1;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public ulong Reserved2;
+
+ ///
+ /// Create a new guest shader cache header.
+ ///
+ /// The count of shaders defining this program
+ /// The count of transform feedback data used in this program
+ 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
new file mode 100644
index 0000000000..18cfdf554b
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestShaderCacheTransformFeedbackHeader.cs
@@ -0,0 +1,38 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Header for transform feedback.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x10)]
+ struct GuestShaderCacheTransformFeedbackHeader
+ {
+ ///
+ /// The buffer index of the transform feedback.
+ ///
+ public int BufferIndex;
+
+ ///
+ /// The stride of the transform feedback.
+ ///
+ public int Stride;
+
+ ///
+ /// The length of the varying location buffer of the transform feedback.
+ ///
+ public int VaryingLocationsLength;
+
+ ///
+ /// Reserved/unused.
+ ///
+ 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
new file mode 100644
index 0000000000..7c73ef7bcf
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/GuestTextureDescriptor.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Graphics.Gpu.Image;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Mostly identical to TextureDescriptor from but we don't store the address of the texture and store its handle instead.
+ ///
+ [StructLayout(LayoutKind.Sequential, Size = 0x20, Pack = 1)]
+ struct GuestTextureDescriptor
+ {
+ public uint Handle;
+ internal TextureDescriptor Descriptor;
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
new file mode 100644
index 0000000000..f592919fc1
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntry.cs
@@ -0,0 +1,210 @@
+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
+{
+ ///
+ /// Host shader entry used for binding information.
+ ///
+ class HostShaderCacheEntry
+ {
+ ///
+ /// The header of the cached shader entry.
+ ///
+ public HostShaderCacheEntryHeader Header { get; }
+
+ ///
+ /// Cached constant buffers.
+ ///
+ public BufferDescriptor[] CBuffers { get; }
+
+ ///
+ /// Cached storage buffers.
+ ///
+ public BufferDescriptor[] SBuffers { get; }
+
+ ///
+ /// Cached texture descriptors.
+ ///
+ public TextureDescriptor[] Textures { get; }
+
+ ///
+ /// Cached image descriptors.
+ ///
+ public TextureDescriptor[] Images { get; }
+
+ ///
+ /// Create a new instance of .
+ ///
+ /// The header of the cached shader entry
+ /// Cached constant buffers
+ /// Cached storage buffers
+ /// Cached texture descriptors
+ /// Cached image descriptors
+ 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);
+ CBuffers = programInfo.CBuffers.ToArray();
+ SBuffers = programInfo.SBuffers.ToArray();
+ Textures = programInfo.Textures.ToArray();
+ Images = programInfo.Images.ToArray();
+ }
+
+ ///
+ /// Convert the host shader entry to a .
+ ///
+ /// A new from this instance
+ internal ShaderProgramInfo ToShaderProgramInfo()
+ {
+ return new ShaderProgramInfo(CBuffers, SBuffers, Textures, Images, Header.UsesInstanceId);
+ }
+
+ ///
+ /// Parse a raw cached user shader program into an array of shader cache entry.
+ ///
+ /// The raw cached host shader
+ /// The host shader program
+ /// An array of shader cache entry
+ internal static HostShaderCacheEntry[] Parse(ReadOnlySpan data, out ReadOnlySpan programCode)
+ {
+ HostShaderCacheHeader fileHeader = MemoryMarshal.Read(data);
+
+ data = data.Slice(Unsafe.SizeOf());
+
+ ReadOnlySpan entryHeaders = MemoryMarshal.Cast(data.Slice(0, fileHeader.Count * Unsafe.SizeOf()));
+
+ data = data.Slice(fileHeader.Count * Unsafe.SizeOf());
+
+ 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();
+ int sBufferDescriptorsSize = header.SBuffersCount * Unsafe.SizeOf();
+ int textureDescriptorsSize = header.TexturesCount * Unsafe.SizeOf();
+ int imageDescriptorsSize = header.ImagesCount * Unsafe.SizeOf();
+
+ ReadOnlySpan cBuffers = MemoryMarshal.Cast(data.Slice(0, cBufferDescriptorsSize));
+ data = data.Slice(cBufferDescriptorsSize);
+
+ ReadOnlySpan sBuffers = MemoryMarshal.Cast(data.Slice(0, sBufferDescriptorsSize));
+ data = data.Slice(sBufferDescriptorsSize);
+
+ ReadOnlySpan textureDescriptors = MemoryMarshal.Cast(data.Slice(0, textureDescriptorsSize));
+ data = data.Slice(textureDescriptorsSize);
+
+ ReadOnlySpan imageDescriptors = MemoryMarshal.Cast(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;
+ }
+
+ ///
+ /// Create a new host shader cache file.
+ ///
+ /// The host shader program
+ /// The shaders code holder
+ /// Raw data of a new host shader cache file
+ internal static byte[] Create(ReadOnlySpan programCode, ShaderCodeHolder[] 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
new file mode 100644
index 0000000000..9b1af8fb2c
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheEntryHeader.cs
@@ -0,0 +1,67 @@
+using System.Runtime.InteropServices;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// Host shader entry header used for binding information.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 1, Size = 0x14)]
+ struct HostShaderCacheEntryHeader
+ {
+ ///
+ /// Count of constant buffer descriptors.
+ ///
+ public int CBuffersCount;
+
+ ///
+ /// Count of storage buffer descriptors.
+ ///
+ public int SBuffersCount;
+
+ ///
+ /// Count of texture descriptors.
+ ///
+ public int TexturesCount;
+
+ ///
+ /// Count of image descriptors.
+ ///
+ public int ImagesCount;
+
+ ///
+ /// Set to true if the shader uses instance id.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool UsesInstanceId;
+
+ ///
+ /// Set to true if this entry is in use.
+ ///
+ [MarshalAs(UnmanagedType.I1)]
+ public bool InUse;
+
+ ///
+ /// Reserved / unused.
+ ///
+ public short Reserved;
+
+ ///
+ /// Create a new host shader cache entry header.
+ ///
+ /// Count of constant buffer descriptors
+ /// Count of storage buffer descriptors
+ /// Count of texture descriptors
+ /// Count of image descriptors
+ /// Set to true if the shader uses instance id
+ public HostShaderCacheEntryHeader(int cBuffersCount, int sBuffersCount, int texturesCount, int imagesCount, bool usesInstanceId) : this()
+ {
+ CBuffersCount = cBuffersCount;
+ SBuffersCount = sBuffersCount;
+ TexturesCount = texturesCount;
+ ImagesCount = imagesCount;
+ UsesInstanceId = usesInstanceId;
+ InUse = true;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs
new file mode 100644
index 0000000000..27f216cca7
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/Cache/Definition/HostShaderCacheHeader.cs
@@ -0,0 +1,42 @@
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader.Cache.Definition
+{
+ ///
+ /// The header of a shader program in the guest cache.
+ ///
+ [StructLayout(LayoutKind.Sequential, Pack = 0x1, Size = 0x10)]
+ struct HostShaderCacheHeader
+ {
+ ///
+ /// The count of shaders defining this program.
+ ///
+ public byte Count;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public byte Reserved1;
+
+ ///
+ /// Unused/reserved.
+ ///
+ public ushort Reserved2;
+
+ ///
+ /// Size of the shader binary.
+ ///
+ public int CodeSize;
+
+ ///
+ /// Create a new host shader cache header.
+ ///
+ /// The count of shaders defining this program
+ /// The size of the shader binary
+ public HostShaderCacheHeader(byte count, int codeSize) : this()
+ {
+ Count = count;
+ CodeSize = codeSize;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
new file mode 100644
index 0000000000..5f1458fa01
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/CachedGpuAccessor.cs
@@ -0,0 +1,154 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ class CachedGpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
+ {
+ private readonly GpuContext _context;
+ private readonly ReadOnlyMemory _data;
+ private readonly GuestGpuAccessorHeader _header;
+ private readonly Dictionary _textureDescriptors;
+
+ ///
+ /// Creates a new instance of the cached GPU state accessor for shader translation.
+ ///
+ /// GPU context
+ /// The data of the shader
+ /// The cache of the GPU accessor
+ /// The cache of the texture descriptors
+ public CachedGpuAccessor(GpuContext context, ReadOnlyMemory data, GuestGpuAccessorHeader header, Dictionary guestTextureDescriptors)
+ {
+ _context = context;
+ _data = data;
+ _header = header;
+ _textureDescriptors = new Dictionary();
+
+ foreach (KeyValuePair guestTextureDescriptor in guestTextureDescriptors)
+ {
+ _textureDescriptors.Add(guestTextureDescriptor.Key, guestTextureDescriptor.Value.Descriptor);
+ }
+ }
+
+ ///
+ /// Prints a log message.
+ ///
+ /// Message to print
+ public void Log(string message)
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Shader translator: {message}");
+ }
+
+ ///
+ /// Reads data from GPU memory.
+ ///
+ /// Type of the data to be read
+ /// GPU virtual address of the data
+ /// Data at the memory location
+ public override T MemoryRead(ulong address)
+ {
+ return MemoryMarshal.Cast(_data.Span.Slice((int)address))[0];
+ }
+
+ ///
+ /// Checks if a given memory address is mapped.
+ ///
+ /// GPU virtual address to be checked
+ /// True if the address is mapped, false otherwise
+ public bool MemoryMapped(ulong address)
+ {
+ return address < (ulong)_data.Length;
+ }
+
+ ///
+ /// Queries Local Size X for compute shaders.
+ ///
+ /// Local Size X
+ public int QueryComputeLocalSizeX()
+ {
+ return _header.ComputeLocalSizeX;
+ }
+
+ ///
+ /// Queries Local Size Y for compute shaders.
+ ///
+ /// Local Size Y
+ public int QueryComputeLocalSizeY()
+ {
+ return _header.ComputeLocalSizeY;
+ }
+
+ ///
+ /// Queries Local Size Z for compute shaders.
+ ///
+ /// Local Size Z
+ public int QueryComputeLocalSizeZ()
+ {
+ return _header.ComputeLocalSizeZ;
+ }
+
+ ///
+ /// Queries Local Memory size in bytes for compute shaders.
+ ///
+ /// Local Memory size in bytes
+ public int QueryComputeLocalMemorySize()
+ {
+ return _header.ComputeLocalMemorySize;
+ }
+
+ ///
+ /// Queries Shared Memory size in bytes for compute shaders.
+ ///
+ /// Shared Memory size in bytes
+ public int QueryComputeSharedMemorySize()
+ {
+ return _header.ComputeSharedMemorySize;
+ }
+
+ ///
+ /// Queries current primitive topology for geometry shaders.
+ ///
+ /// Current primitive topology
+ public InputTopology QueryPrimitiveTopology()
+ {
+ return _header.PrimitiveTopology;
+ }
+
+ ///
+ /// Queries host storage buffer alignment required.
+ ///
+ /// Host storage buffer alignment in bytes
+ public int QueryStorageBufferOffsetAlignment() => _context.Capabilities.StorageBufferOffsetAlignment;
+
+ ///
+ /// Queries host support for readable images without a explicit format declaration on the shader.
+ ///
+ /// True if formatted image load is supported, false otherwise
+ public bool QuerySupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
+
+ ///
+ /// Queries host GPU non-constant texture offset support.
+ ///
+ /// True if the GPU and driver supports non-constant texture offsets, false otherwise
+ public bool QuerySupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
+
+ ///
+ /// Gets the texture descriptor for a given texture on the pool.
+ ///
+ /// Index of the texture (this is the word offset of the handle in the constant buffer)
+ /// Texture descriptor
+ public override Image.TextureDescriptor GetTextureDescriptor(int handle)
+ {
+ if (!_textureDescriptors.TryGetValue(handle, out Image.TextureDescriptor textureDescriptor))
+ {
+ throw new ArgumentException();
+ }
+
+ return textureDescriptor;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
index 0eaa534b7f..b3f1b3a86b 100644
--- a/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/GpuAccessor.cs
@@ -1,6 +1,5 @@
using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
-using Ryujinx.Graphics.Gpu.Image;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
@@ -9,7 +8,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
/// Represents a GPU state and memory accessor.
///
- class GpuAccessor : IGpuAccessor
+ class GpuAccessor : TextureDescriptorCapableGpuAccessor, IGpuAccessor
{
private readonly GpuContext _context;
private readonly GpuState _state;
@@ -78,7 +77,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Type of the data to be read
/// GPU virtual address of the data
/// Data at the memory location
- public T MemoryRead(ulong address) where T : unmanaged
+ public override T MemoryRead(ulong address)
{
return _context.MemoryManager.Read(address);
}
@@ -134,33 +133,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
: _context.Methods.BufferManager.GetGraphicsUniformBufferUseMask(_stageIndex);
}
- ///
- /// Queries texture target information.
- ///
- /// Texture handle
- /// True if the texture is a buffer texture, false otherwise
- public bool QueryIsTextureBuffer(int handle)
- {
- return GetTextureDescriptor(handle).UnpackTextureTarget() == TextureTarget.TextureBuffer;
- }
-
- ///
- /// Queries texture target information.
- ///
- /// Texture handle
- /// True if the texture is a rectangle texture, false otherwise
- public bool QueryIsTextureRectangle(int handle)
- {
- var descriptor = GetTextureDescriptor(handle);
-
- TextureTarget target = descriptor.UnpackTextureTarget();
-
- bool is2DTexture = target == TextureTarget.Texture2D ||
- target == TextureTarget.Texture2DRect;
-
- return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
- }
-
///
/// Queries current primitive topology for geometry shaders.
///
@@ -208,76 +180,12 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// True if the GPU and driver supports non-constant texture offsets, false otherwise
public bool QuerySupportsNonConstantTextureOffset() => _context.Capabilities.SupportsNonConstantTextureOffset;
- ///
- /// Queries texture format information, for shaders using image load or store.
- ///
- ///
- /// This only returns non-compressed color formats.
- /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
- ///
- /// Texture handle
- /// Color format of the non-compressed texture
- public TextureFormat QueryTextureFormat(int handle)
- {
- var descriptor = GetTextureDescriptor(handle);
-
- if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
- {
- return TextureFormat.Unknown;
- }
-
- return formatInfo.Format switch
- {
- Format.R8Unorm => TextureFormat.R8Unorm,
- Format.R8Snorm => TextureFormat.R8Snorm,
- Format.R8Uint => TextureFormat.R8Uint,
- Format.R8Sint => TextureFormat.R8Sint,
- Format.R16Float => TextureFormat.R16Float,
- Format.R16Unorm => TextureFormat.R16Unorm,
- Format.R16Snorm => TextureFormat.R16Snorm,
- Format.R16Uint => TextureFormat.R16Uint,
- Format.R16Sint => TextureFormat.R16Sint,
- Format.R32Float => TextureFormat.R32Float,
- Format.R32Uint => TextureFormat.R32Uint,
- Format.R32Sint => TextureFormat.R32Sint,
- Format.R8G8Unorm => TextureFormat.R8G8Unorm,
- Format.R8G8Snorm => TextureFormat.R8G8Snorm,
- Format.R8G8Uint => TextureFormat.R8G8Uint,
- Format.R8G8Sint => TextureFormat.R8G8Sint,
- Format.R16G16Float => TextureFormat.R16G16Float,
- Format.R16G16Unorm => TextureFormat.R16G16Unorm,
- Format.R16G16Snorm => TextureFormat.R16G16Snorm,
- Format.R16G16Uint => TextureFormat.R16G16Uint,
- Format.R16G16Sint => TextureFormat.R16G16Sint,
- Format.R32G32Float => TextureFormat.R32G32Float,
- Format.R32G32Uint => TextureFormat.R32G32Uint,
- Format.R32G32Sint => TextureFormat.R32G32Sint,
- Format.R8G8B8A8Unorm => TextureFormat.R8G8B8A8Unorm,
- Format.R8G8B8A8Snorm => TextureFormat.R8G8B8A8Snorm,
- Format.R8G8B8A8Uint => TextureFormat.R8G8B8A8Uint,
- Format.R8G8B8A8Sint => TextureFormat.R8G8B8A8Sint,
- Format.R8G8B8A8Srgb => TextureFormat.R8G8B8A8Unorm,
- Format.R16G16B16A16Float => TextureFormat.R16G16B16A16Float,
- Format.R16G16B16A16Unorm => TextureFormat.R16G16B16A16Unorm,
- Format.R16G16B16A16Snorm => TextureFormat.R16G16B16A16Snorm,
- Format.R16G16B16A16Uint => TextureFormat.R16G16B16A16Uint,
- Format.R16G16B16A16Sint => TextureFormat.R16G16B16A16Sint,
- Format.R32G32B32A32Float => TextureFormat.R32G32B32A32Float,
- Format.R32G32B32A32Uint => TextureFormat.R32G32B32A32Uint,
- Format.R32G32B32A32Sint => TextureFormat.R32G32B32A32Sint,
- Format.R10G10B10A2Unorm => TextureFormat.R10G10B10A2Unorm,
- Format.R10G10B10A2Uint => TextureFormat.R10G10B10A2Uint,
- Format.R11G11B10Float => TextureFormat.R11G11B10Float,
- _ => TextureFormat.Unknown
- };
- }
-
///
/// Gets the texture descriptor for a given texture on the pool.
///
- /// Index of the texture (this is the shader "fake" handle)
+ /// Index of the texture (this is the word offset of the handle in the constant buffer)
/// Texture descriptor
- private Image.TextureDescriptor GetTextureDescriptor(int handle)
+ public override Image.TextureDescriptor GetTextureDescriptor(int handle)
{
if (_compute)
{
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs
index de06e5e079..efdbc3ebe0 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderBundle.cs
@@ -39,7 +39,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
foreach (ShaderCodeHolder holder in Shaders)
{
- holder?.HostShader.Dispose();
+ holder?.HostShader?.Dispose();
}
}
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
index 131aa6b7f5..ac5aedbeb3 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCache.cs
@@ -1,9 +1,17 @@
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Shader.Cache;
+using Ryujinx.Graphics.Gpu.Shader.Cache.Definition;
using Ryujinx.Graphics.Gpu.State;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using System;
using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Gpu.Shader
{
@@ -21,6 +29,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
private readonly Dictionary> _cpPrograms;
private readonly Dictionary> _gpPrograms;
+ private CacheManager _cacheManager;
+
+ private Dictionary _gpProgramsDiskCache;
+ private Dictionary _cpProgramsDiskCache;
+
+ ///
+ /// Version of the codegen (to be incremented when codegen changes).
+ ///
+ private const ulong ShaderCodeGenVersion = 1;
+
///
/// Creates a new instance of the shader cache.
///
@@ -33,6 +51,251 @@ namespace Ryujinx.Graphics.Gpu.Shader
_cpPrograms = new Dictionary>();
_gpPrograms = new Dictionary>();
+ _gpProgramsDiskCache = new Dictionary();
+ _cpProgramsDiskCache = new Dictionary();
+ }
+
+ ///
+ /// Initialize the cache.
+ ///
+ internal void Initialize()
+ {
+ if (GraphicsConfig.EnableShaderCache && GraphicsConfig.TitleId != null)
+ {
+ _cacheManager = new CacheManager(CacheGraphicsApi.OpenGL, CacheHashType.XxHash128, "glsl", GraphicsConfig.TitleId, ShaderCodeGenVersion);
+
+ HashSet invalidEntries = new HashSet();
+
+ ReadOnlySpan guestProgramList = _cacheManager.GetGuestProgramList();
+
+ for (int programIndex = 0; programIndex < guestProgramList.Length; programIndex++)
+ {
+ Hash128 key = guestProgramList[programIndex];
+
+ Logger.Info?.Print(LogClass.Gpu, $"Compiling shader {key} ({programIndex + 1} / {guestProgramList.Length})");
+
+ byte[] hostProgramBinary = _cacheManager.GetHostProgramByHash(ref key);
+ bool hasHostCache = hostProgramBinary != null;
+
+ IProgram hostProgram = null;
+
+ // If the program sources aren't in the cache, compile from saved guest program.
+ 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?)");
+
+ // Should not happen, but if someone messed with the cache it's better to catch it.
+ invalidEntries.Add(key);
+
+ continue;
+ }
+
+ ReadOnlySpan guestProgramReadOnlySpan = guestProgram;
+
+ ReadOnlySpan cachedShaderEntries = GuestShaderCacheEntry.Parse(ref guestProgramReadOnlySpan, out GuestShaderCacheHeader fileHeader);
+
+ if (cachedShaderEntries[0].Header.Stage == ShaderStage.Compute)
+ {
+ Debug.Assert(cachedShaderEntries.Length == 1);
+
+ GuestShaderCacheEntry entry = cachedShaderEntries[0];
+
+ HostShaderCacheEntry[] hostShaderEntries = null;
+
+ // Try loading host shader binary.
+ if (hasHostCache)
+ {
+ hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan hostProgramBinarySpan);
+ hostProgramBinary = hostProgramBinarySpan.ToArray();
+ hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
+ }
+
+ bool isHostProgramValid = hostProgram != null;
+
+ ShaderProgram program;
+ ShaderProgramInfo shaderProgramInfo;
+
+ // Reconstruct code holder.
+ if (isHostProgramValid)
+ {
+ program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA);
+ shaderProgramInfo = hostShaderEntries[0].ToShaderProgramInfo();
+ }
+ else
+ {
+ IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
+
+ program = Translator.CreateContext(0, gpuAccessor, DefaultFlags | TranslationFlags.Compute).Translate(out shaderProgramInfo);
+ }
+
+ ShaderCodeHolder shader = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
+
+ // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
+ if (hostProgram == null)
+ {
+ Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
+
+ // Compile shader and create program as the shader program binary got invalidated.
+ shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
+ hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
+
+ // As the host program was invalidated, save the new entry in the cache.
+ hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
+
+ if (hasHostCache)
+ {
+ _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
+
+ _cacheManager.AddHostProgram(ref key, hostProgramBinary);
+ }
+ }
+
+ _cpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shader));
+ }
+ else
+ {
+ Debug.Assert(cachedShaderEntries.Length == Constants.ShaderStages);
+
+ ShaderCodeHolder[] shaders = new ShaderCodeHolder[cachedShaderEntries.Length];
+ List shaderPrograms = new List();
+
+ TransformFeedbackDescriptor[] tfd = ReadTransformationFeedbackInformations(ref guestProgramReadOnlySpan, fileHeader);
+
+ TranslationFlags flags = DefaultFlags;
+
+ if (tfd != null)
+ {
+ flags = TranslationFlags.Feedback;
+ }
+
+ TranslationCounts counts = new TranslationCounts();
+
+ HostShaderCacheEntry[] hostShaderEntries = null;
+
+ // Try loading host shader binary.
+ if (hasHostCache)
+ {
+ hostShaderEntries = HostShaderCacheEntry.Parse(hostProgramBinary, out ReadOnlySpan hostProgramBinarySpan);
+ hostProgramBinary = hostProgramBinarySpan.ToArray();
+ hostProgram = _context.Renderer.LoadProgramBinary(hostProgramBinary);
+ }
+
+ bool isHostProgramValid = hostProgram != null;
+
+ // Reconstruct code holder.
+ for (int i = 0; i < cachedShaderEntries.Length; i++)
+ {
+ GuestShaderCacheEntry entry = cachedShaderEntries[i];
+
+ if (entry == null)
+ {
+ continue;
+ }
+
+ ShaderProgram program;
+
+ if (entry.Header.SizeA != 0)
+ {
+ ShaderProgramInfo shaderProgramInfo;
+
+ if (isHostProgramValid)
+ {
+ program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA);
+ shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
+ }
+ else
+ {
+ IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
+
+ program = Translator.CreateContext((ulong)entry.Header.Size, 0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo);
+ }
+
+ // NOTE: Vertex B comes first in the shader cache.
+ byte[] code = entry.Code.AsSpan().Slice(0, entry.Header.Size).ToArray();
+ byte[] code2 = entry.Code.AsSpan().Slice(entry.Header.Size, entry.Header.SizeA).ToArray();
+
+ shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, code, code2);
+ }
+ else
+ {
+ ShaderProgramInfo shaderProgramInfo;
+
+ if (isHostProgramValid)
+ {
+ program = new ShaderProgram(entry.Header.Stage, "", entry.Header.Size, entry.Header.SizeA);
+ shaderProgramInfo = hostShaderEntries[i].ToShaderProgramInfo();
+ }
+ else
+ {
+ IGpuAccessor gpuAccessor = new CachedGpuAccessor(_context, entry.Code, entry.Header.GpuAccessorHeader, entry.TextureDescriptors);
+
+ program = Translator.CreateContext(0, gpuAccessor, flags, counts).Translate(out shaderProgramInfo);
+ }
+
+ shaders[i] = new ShaderCodeHolder(program, shaderProgramInfo, entry.Code);
+ }
+
+ shaderPrograms.Add(program);
+ }
+
+ // If the host program was rejected by the gpu driver or isn't in cache, try to build from program sources again.
+ if (!isHostProgramValid)
+ {
+ Logger.Info?.Print(LogClass.Gpu, $"Host shader {key} got invalidated, rebuilding from guest...");
+
+ List hostShaders = new List();
+
+ // Compile shaders and create program as the shader program binary got invalidated.
+ for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ {
+ ShaderProgram program = shaders[stage]?.Program;
+
+ if (program == null)
+ {
+ continue;
+ }
+
+ IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
+
+ shaders[stage].HostShader = hostShader;
+
+ hostShaders.Add(hostShader);
+ }
+
+ hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
+
+ // As the host program was invalidated, save the new entry in the cache.
+ hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
+
+ if (hasHostCache)
+ {
+ _cacheManager.ReplaceHostProgram(ref key, hostProgramBinary);
+ }
+ else
+ {
+ Logger.Warning?.Print(LogClass.Gpu, $"Add missing host shader {key} in cache (is the cache incomplete?)");
+
+ _cacheManager.AddHostProgram(ref key, hostProgramBinary);
+ }
+ }
+
+ _gpProgramsDiskCache.Add(key, new ShaderBundle(hostProgram, shaders));
+ }
+ }
+
+ // Remove entries that are broken in the cache
+ _cacheManager.RemoveManifestEntries(invalidEntries);
+ _cacheManager.FlushToArchive();
+ _cacheManager.Synchronize();
+
+ Logger.Info?.Print(LogClass.Gpu, "Shader cache loaded.");
+ }
}
///
@@ -71,7 +334,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
- ShaderCodeHolder shader = TranslateComputeShader(
+ TranslatorContext[] shaderContexts = new TranslatorContext[1];
+
+ shaderContexts[0] = DecodeComputeShader(
state,
gpuVa,
localSizeX,
@@ -80,11 +345,45 @@ namespace Ryujinx.Graphics.Gpu.Shader
localMemorySize,
sharedMemorySize);
- shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
+ bool isShaderCacheEnabled = _cacheManager != null;
- IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
+ byte[] programCode = null;
+ Hash128 programCodeHash = default;
+ GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
- ShaderBundle cpShader = new ShaderBundle(hostProgram, shader);
+ if (isShaderCacheEnabled)
+ {
+ // Compute hash and prepare data for shader disk cache comparison.
+ GetProgramInformations(null, shaderContexts, out programCode, out programCodeHash, out shaderCacheEntries);
+ }
+
+ ShaderBundle cpShader;
+
+ // Search for the program hash in loaded shaders.
+ if (!isShaderCacheEnabled || !_cpProgramsDiskCache.TryGetValue(programCodeHash, out cpShader))
+ {
+ if (isShaderCacheEnabled)
+ {
+ Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
+ }
+
+ // The shader isn't currently cached, translate it and compile it.
+ ShaderCodeHolder shader = TranslateShader(shaderContexts[0]);
+
+ shader.HostShader = _context.Renderer.CompileShader(ShaderStage.Compute, shader.Program.Code);
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(new IShader[] { shader.HostShader }, null);
+
+ byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), new ShaderCodeHolder[] { shader });
+
+ cpShader = new ShaderBundle(hostProgram, shader);
+
+ if (isShaderCacheEnabled)
+ {
+ _cpProgramsDiskCache.Add(programCodeHash, cpShader);
+ _cacheManager.SaveProgram(ref programCodeHash, CreateGuestProgramDump(programCode, shaderCacheEntries, null), hostProgramBinary);
+ }
+ }
if (!isCached)
{
@@ -123,9 +422,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
}
- ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
+ TranslatorContext[] shaderContexts = new TranslatorContext[Constants.ShaderStages];
- var tfd = GetTransformFeedbackDescriptors(state);
+ TransformFeedbackDescriptor[] tfd = GetTransformFeedbackDescriptors(state);
TranslationFlags flags = DefaultFlags;
@@ -138,40 +437,80 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (addresses.VertexA != 0)
{
- shaders[0] = TranslateGraphicsShader(state, counts, flags, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
+ shaderContexts[0] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Vertex, addresses.Vertex, addresses.VertexA);
}
else
{
- shaders[0] = TranslateGraphicsShader(state, counts, flags, ShaderStage.Vertex, addresses.Vertex);
+ shaderContexts[0] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Vertex, addresses.Vertex);
}
- shaders[1] = TranslateGraphicsShader(state, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
- shaders[2] = TranslateGraphicsShader(state, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
- shaders[3] = TranslateGraphicsShader(state, counts, flags, ShaderStage.Geometry, addresses.Geometry);
- shaders[4] = TranslateGraphicsShader(state, counts, flags, ShaderStage.Fragment, addresses.Fragment);
+ shaderContexts[1] = DecodeGraphicsShader(state, counts, flags, ShaderStage.TessellationControl, addresses.TessControl);
+ shaderContexts[2] = DecodeGraphicsShader(state, counts, flags, ShaderStage.TessellationEvaluation, addresses.TessEvaluation);
+ shaderContexts[3] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Geometry, addresses.Geometry);
+ shaderContexts[4] = DecodeGraphicsShader(state, counts, flags, ShaderStage.Fragment, addresses.Fragment);
- List hostShaders = new List();
+ bool isShaderCacheEnabled = _cacheManager != null;
- for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ byte[] programCode = null;
+ Hash128 programCodeHash = default;
+ GuestShaderCacheEntryHeader[] shaderCacheEntries = null;
+
+ if (isShaderCacheEnabled)
{
- ShaderProgram program = shaders[stage]?.Program;
+ // Compute hash and prepare data for shader disk cache comparison.
+ GetProgramInformations(tfd, shaderContexts, out programCode, out programCodeHash, out shaderCacheEntries);
+ }
- if (program == null)
+ ShaderBundle gpShaders;
+
+ // Search for the program hash in loaded shaders.
+ if (!isShaderCacheEnabled || !_gpProgramsDiskCache.TryGetValue(programCodeHash, out gpShaders))
+ {
+ if (isShaderCacheEnabled)
{
- continue;
+ Logger.Debug?.Print(LogClass.Gpu, $"Shader {programCodeHash} not in cache, compiling!");
}
- IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
+ // The shader isn't currently cached, translate it and compile it.
+ ShaderCodeHolder[] shaders = new ShaderCodeHolder[Constants.ShaderStages];
- shaders[stage].HostShader = hostShader;
+ shaders[0] = TranslateShader(shaderContexts[0]);
+ shaders[1] = TranslateShader(shaderContexts[1]);
+ shaders[2] = TranslateShader(shaderContexts[2]);
+ shaders[3] = TranslateShader(shaderContexts[3]);
+ shaders[4] = TranslateShader(shaderContexts[4]);
- hostShaders.Add(hostShader);
+ List hostShaders = new List();
+
+ for (int stage = 0; stage < Constants.ShaderStages; stage++)
+ {
+ ShaderProgram program = shaders[stage]?.Program;
+
+ if (program == null)
+ {
+ continue;
+ }
+
+ IShader hostShader = _context.Renderer.CompileShader(program.Stage, program.Code);
+
+ shaders[stage].HostShader = hostShader;
+
+ hostShaders.Add(hostShader);
+ }
+
+ IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
+
+ byte[] hostProgramBinary = HostShaderCacheEntry.Create(hostProgram.GetBinary(), shaders);
+
+ gpShaders = new ShaderBundle(hostProgram, shaders);
+
+ if (isShaderCacheEnabled)
+ {
+ _gpProgramsDiskCache.Add(programCodeHash, gpShaders);
+ _cacheManager.SaveProgram(ref programCodeHash, CreateGuestProgramDump(programCode, shaderCacheEntries, tfd), hostProgramBinary);
+ }
}
- IProgram hostProgram = _context.Renderer.CreateProgram(hostShaders.ToArray(), tfd);
-
- ShaderBundle gpShaders = new ShaderBundle(hostProgram, shaders);
-
if (!isCached)
{
list = new List();
@@ -286,7 +625,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
}
///
- /// Translates the binary Maxwell shader code to something that the host API accepts.
+ /// Decode the binary Maxwell shader code to a translator context.
///
/// Current GPU state
/// GPU virtual address of the binary shader code
@@ -295,8 +634,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Local group size Z of the computer shader
/// Local memory size of the compute shader
/// Shared memory size of the compute shader
- /// Compiled compute shader code
- private ShaderCodeHolder TranslateComputeShader(
+ /// The generated translator context
+ private TranslatorContext DecodeComputeShader(
GpuState state,
ulong gpuVa,
int localSizeX,
@@ -312,25 +651,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
GpuAccessor gpuAccessor = new GpuAccessor(_context, state, localSizeX, localSizeY, localSizeZ, localMemorySize, sharedMemorySize);
- ShaderProgram program;
-
- program = Translator.Translate(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
-
- byte[] code = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
-
- _dumper.Dump(code, compute: true, out string fullPath, out string codePath);
-
- if (fullPath != null && codePath != null)
- {
- program.Prepend("// " + codePath);
- program.Prepend("// " + fullPath);
- }
-
- return new ShaderCodeHolder(program, code);
+ return Translator.CreateContext(gpuVa, gpuAccessor, DefaultFlags | TranslationFlags.Compute);
}
///
- /// Translates the binary Maxwell shader code to something that the host API accepts.
+ /// Decode the binary Maxwell shader code to a translator context.
///
///
/// This will combine the "Vertex A" and "Vertex B" shader stages, if specified, into one shader.
@@ -341,8 +666,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Shader stage
/// GPU virtual address of the shader code
/// Optional GPU virtual address of the "Vertex A" shader code
- /// Compiled graphics shader code
- private ShaderCodeHolder TranslateGraphicsShader(
+ /// The generated translator context
+ private TranslatorContext DecodeGraphicsShader(
GpuState state,
TranslationCounts counts,
TranslationFlags flags,
@@ -359,14 +684,36 @@ namespace Ryujinx.Graphics.Gpu.Shader
if (gpuVaA != 0)
{
- ShaderProgram program = Translator.Translate(gpuVaA, gpuVa, gpuAccessor, flags, counts);
+ return Translator.CreateContext(gpuVaA, gpuVa, gpuAccessor, flags, counts);
+ }
+ else
+ {
+ return Translator.CreateContext(gpuVa, gpuAccessor, flags, counts);
+ }
+ }
- byte[] codeA = _context.MemoryManager.GetSpan(gpuVaA, program.SizeA).ToArray();
- byte[] codeB = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
+ ///
+ /// Translates a previously generated translator context to something that the host API accepts.
+ ///
+ /// Current translator context to translate
+ /// Compiled graphics shader code
+ private ShaderCodeHolder TranslateShader(TranslatorContext translatorContext)
+ {
+ if (translatorContext == null)
+ {
+ return null;
+ }
+
+ if (translatorContext.AddressA != 0)
+ {
+ byte[] codeA = _context.MemoryManager.GetSpan(translatorContext.AddressA, translatorContext.SizeA).ToArray();
+ byte[] codeB = _context.MemoryManager.GetSpan(translatorContext.Address, translatorContext.Size).ToArray();
_dumper.Dump(codeA, compute: false, out string fullPathA, out string codePathA);
_dumper.Dump(codeB, compute: false, out string fullPathB, out string codePathB);
+ ShaderProgram program = translatorContext.Translate(out ShaderProgramInfo shaderProgramInfo);
+
if (fullPathA != null && fullPathB != null && codePathA != null && codePathB != null)
{
program.Prepend("// " + codePathB);
@@ -375,23 +722,23 @@ namespace Ryujinx.Graphics.Gpu.Shader
program.Prepend("// " + fullPathA);
}
- return new ShaderCodeHolder(program, codeB, codeA);
+ return new ShaderCodeHolder(program, shaderProgramInfo, codeB, codeA);
}
else
{
- ShaderProgram program = Translator.Translate(gpuVa, gpuAccessor, flags, counts);
-
- byte[] code = _context.MemoryManager.GetSpan(gpuVa, program.Size).ToArray();
+ byte[] code = _context.MemoryManager.GetSpan(translatorContext.Address, translatorContext.Size).ToArray();
_dumper.Dump(code, compute: false, out string fullPath, out string codePath);
+ ShaderProgram program = translatorContext.Translate(out ShaderProgramInfo shaderProgramInfo);
+
if (fullPath != null && codePath != null)
{
program.Prepend("// " + codePath);
program.Prepend("// " + fullPath);
}
- return new ShaderCodeHolder(program, code);
+ return new ShaderCodeHolder(program, shaderProgramInfo, code);
}
}
@@ -416,6 +763,194 @@ namespace Ryujinx.Graphics.Gpu.Shader
bundle.Dispose();
}
}
+
+ _cacheManager?.Dispose();
+ }
+
+ ///
+ /// Create a guest shader program.
+ ///
+ /// The program code of the shader code
+ /// The resulting guest shader entries header
+ /// The transform feedback descriptors in use
+ /// The resulting guest shader program
+ private static byte[] CreateGuestProgramDump(ReadOnlySpan programCode, GuestShaderCacheEntryHeader[] shaderCacheEntries, TransformFeedbackDescriptor[] tfd)
+ {
+ using (MemoryStream resultStream = new MemoryStream())
+ {
+ BinaryWriter resultStreamWriter = new BinaryWriter(resultStream);
+
+ byte transformFeedbackCount = 0;
+
+ if (tfd != null)
+ {
+ transformFeedbackCount = (byte)tfd.Length;
+ }
+
+ // Header
+ resultStreamWriter.WriteStruct(new GuestShaderCacheHeader((byte)shaderCacheEntries.Length, transformFeedbackCount));
+
+ // Write all entries header
+ foreach (GuestShaderCacheEntryHeader entry in shaderCacheEntries)
+ {
+ resultStreamWriter.WriteStruct(entry);
+ }
+
+ // Finally, write all program code and all transform feedback information.
+ resultStreamWriter.Write(programCode);
+
+ return resultStream.ToArray();
+ }
+ }
+
+ ///
+ /// Write transform feedback guest information to the given stream.
+ ///
+ /// The stream to write data to
+ /// The current transform feedback descriptors used
+ private static void WriteTransformationFeedbackInformation(Stream stream, TransformFeedbackDescriptor[] tfd)
+ {
+ if (tfd != null)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ foreach (TransformFeedbackDescriptor transform in tfd)
+ {
+ writer.WriteStruct(new GuestShaderCacheTransformFeedbackHeader(transform.BufferIndex, transform.Stride, transform.VaryingLocations.Length));
+ writer.Write(transform.VaryingLocations);
+ }
+ }
+ }
+
+ ///
+ /// Read transform feedback descriptors from guest.
+ ///
+ /// The raw guest transform feedback descriptors
+ /// The guest shader program header
+ /// The transform feedback descriptors read from guest
+ private static TransformFeedbackDescriptor[] ReadTransformationFeedbackInformations(ref ReadOnlySpan data, GuestShaderCacheHeader header)
+ {
+ if (header.TransformFeedbackCount != 0)
+ {
+ TransformFeedbackDescriptor[] result = new TransformFeedbackDescriptor[header.TransformFeedbackCount];
+
+ for (int i = 0; i < result.Length; i++)
+ {
+ GuestShaderCacheTransformFeedbackHeader feedbackHeader = MemoryMarshal.Read(data);
+
+ result[i] = new TransformFeedbackDescriptor(feedbackHeader.BufferIndex, feedbackHeader.Stride, data.Slice(Unsafe.SizeOf(), feedbackHeader.VaryingLocationsLength).ToArray());
+
+ data = data.Slice(Unsafe.SizeOf() + feedbackHeader.VaryingLocationsLength);
+ }
+
+ return result;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Create a new instance of from an gpu accessor.
+ ///
+ /// The gpu accessor
+ /// a new instance of
+ private static GuestGpuAccessorHeader CreateGuestGpuAccessorCache(IGpuAccessor gpuAccessor)
+ {
+ return new GuestGpuAccessorHeader
+ {
+ ComputeLocalSizeX = gpuAccessor.QueryComputeLocalSizeX(),
+ ComputeLocalSizeY = gpuAccessor.QueryComputeLocalSizeY(),
+ ComputeLocalSizeZ = gpuAccessor.QueryComputeLocalSizeZ(),
+ ComputeLocalMemorySize = gpuAccessor.QueryComputeLocalMemorySize(),
+ ComputeSharedMemorySize = gpuAccessor.QueryComputeSharedMemorySize(),
+ PrimitiveTopology = gpuAccessor.QueryPrimitiveTopology(),
+ };
+ }
+
+ ///
+ /// Write the guest GpuAccessor informations to the given stream.
+ ///
+ /// The stream to write the guest GpuAcessor
+ /// The shader tranlator context in use
+ /// The guest gpu accessor header
+ private static GuestGpuAccessorHeader WriteGuestGpuAccessorCache(Stream stream, TranslatorContext shaderContext)
+ {
+ BinaryWriter writer = new BinaryWriter(stream);
+
+ GuestGpuAccessorHeader header = CreateGuestGpuAccessorCache(shaderContext.GpuAccessor);
+
+ // If we have a full gpu accessor, cache textures descriptors
+ if (shaderContext.GpuAccessor is GpuAccessor gpuAccessor)
+ {
+ HashSet textureHandlesInUse = shaderContext.TextureHandlesForCache;
+
+ header.TextureDescriptorCount = textureHandlesInUse.Count;
+
+ foreach (int textureHandle in textureHandlesInUse)
+ {
+ GuestTextureDescriptor textureDescriptor = gpuAccessor.GetTextureDescriptor(textureHandle).ToCache();
+
+ textureDescriptor.Handle = (uint)textureHandle;
+
+ writer.WriteStruct(textureDescriptor);
+ }
+ }
+
+ return header;
+ }
+
+ ///
+ /// Get the shader program information for use on the shader cache.
+ ///
+ /// The current transform feedback descriptors used
+ /// The shader translators context in use
+ /// The resulting raw shader program code
+ /// The resulting raw shader program code hash
+ /// The resulting guest shader entries header
+ private void GetProgramInformations(TransformFeedbackDescriptor[] tfd, ReadOnlySpan shaderContexts, out byte[] programCode, out Hash128 programCodeHash, out GuestShaderCacheEntryHeader[] entries)
+ {
+ GuestShaderCacheEntryHeader ComputeStage(Stream stream, TranslatorContext context)
+ {
+ if (context == null)
+ {
+ return new GuestShaderCacheEntryHeader();
+ }
+
+ ReadOnlySpan data = _context.MemoryManager.GetSpan(context.Address, context.Size);
+
+ stream.Write(data);
+
+ int size = data.Length;
+ int sizeA = 0;
+
+ if (context.AddressA != 0)
+ {
+ data = _context.MemoryManager.GetSpan(context.AddressA, context.SizeA);
+
+ sizeA = data.Length;
+
+ stream.Write(data);
+ }
+
+ GuestGpuAccessorHeader gpuAccessorHeader = WriteGuestGpuAccessorCache(stream, context);
+
+ return new GuestShaderCacheEntryHeader(context.Stage, size, sizeA, gpuAccessorHeader);
+ }
+
+ entries = new GuestShaderCacheEntryHeader[shaderContexts.Length];
+
+ using (MemoryStream stream = new MemoryStream())
+ {
+ for (int i = 0; i < shaderContexts.Length; i++)
+ {
+ entries[i] = ComputeStage(stream, shaderContexts[i]);
+ }
+
+ WriteTransformationFeedbackInformation(stream, tfd);
+
+ programCode = stream.ToArray();
+ programCodeHash = _cacheManager.ComputeHash(programCode);
+ }
}
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs b/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
index dd90788e90..dbf2d6f591 100644
--- a/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
+++ b/Ryujinx.Graphics.Gpu/Shader/ShaderCodeHolder.cs
@@ -13,9 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader
///
public ShaderProgram Program { get; }
+ ///
+ /// Shader program information.
+ ///
+ public ShaderProgramInfo Info { get; }
+
///
/// Host shader object.
///
+ /// Null if the host shader program cache is in use.
public IShader HostShader { get; set; }
///
@@ -32,11 +38,13 @@ namespace Ryujinx.Graphics.Gpu.Shader
/// Creates a new instace of the shader code holder.
///
/// Shader program
+ /// Shader program information
/// Maxwell binary shader code
/// Optional binary shader code of the "Vertex A" shader, when combined with "Vertex B"
- public ShaderCodeHolder(ShaderProgram program, byte[] code, byte[] code2 = null)
+ public ShaderCodeHolder(ShaderProgram program, ShaderProgramInfo info, byte[] code, byte[] code2 = null)
{
Program = program;
+ Info = info;
Code = code;
Code2 = code2;
}
diff --git a/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs b/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
new file mode 100644
index 0000000000..7901fe5937
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Shader/TextureDescriptorCapableGpuAccessor.cs
@@ -0,0 +1,104 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ abstract class TextureDescriptorCapableGpuAccessor : IGpuAccessor
+ {
+ public abstract T MemoryRead(ulong address) where T : unmanaged;
+
+ public abstract Image.TextureDescriptor GetTextureDescriptor(int handle);
+
+ ///
+ /// Queries texture format information, for shaders using image load or store.
+ ///
+ ///
+ /// This only returns non-compressed color formats.
+ /// If the format of the texture is a compressed, depth or unsupported format, then a default value is returned.
+ ///
+ /// Texture handle
+ /// Color format of the non-compressed texture
+ public TextureFormat QueryTextureFormat(int handle)
+ {
+ var descriptor = GetTextureDescriptor(handle);
+
+ if (!FormatTable.TryGetTextureFormat(descriptor.UnpackFormat(), descriptor.UnpackSrgb(), out FormatInfo formatInfo))
+ {
+ return TextureFormat.Unknown;
+ }
+
+ return formatInfo.Format switch
+ {
+ Format.R8Unorm => TextureFormat.R8Unorm,
+ Format.R8Snorm => TextureFormat.R8Snorm,
+ Format.R8Uint => TextureFormat.R8Uint,
+ Format.R8Sint => TextureFormat.R8Sint,
+ Format.R16Float => TextureFormat.R16Float,
+ Format.R16Unorm => TextureFormat.R16Unorm,
+ Format.R16Snorm => TextureFormat.R16Snorm,
+ Format.R16Uint => TextureFormat.R16Uint,
+ Format.R16Sint => TextureFormat.R16Sint,
+ Format.R32Float => TextureFormat.R32Float,
+ Format.R32Uint => TextureFormat.R32Uint,
+ Format.R32Sint => TextureFormat.R32Sint,
+ Format.R8G8Unorm => TextureFormat.R8G8Unorm,
+ Format.R8G8Snorm => TextureFormat.R8G8Snorm,
+ Format.R8G8Uint => TextureFormat.R8G8Uint,
+ Format.R8G8Sint => TextureFormat.R8G8Sint,
+ Format.R16G16Float => TextureFormat.R16G16Float,
+ Format.R16G16Unorm => TextureFormat.R16G16Unorm,
+ Format.R16G16Snorm => TextureFormat.R16G16Snorm,
+ Format.R16G16Uint => TextureFormat.R16G16Uint,
+ Format.R16G16Sint => TextureFormat.R16G16Sint,
+ Format.R32G32Float => TextureFormat.R32G32Float,
+ Format.R32G32Uint => TextureFormat.R32G32Uint,
+ Format.R32G32Sint => TextureFormat.R32G32Sint,
+ Format.R8G8B8A8Unorm => TextureFormat.R8G8B8A8Unorm,
+ Format.R8G8B8A8Snorm => TextureFormat.R8G8B8A8Snorm,
+ Format.R8G8B8A8Uint => TextureFormat.R8G8B8A8Uint,
+ Format.R8G8B8A8Sint => TextureFormat.R8G8B8A8Sint,
+ Format.R8G8B8A8Srgb => TextureFormat.R8G8B8A8Unorm,
+ Format.R16G16B16A16Float => TextureFormat.R16G16B16A16Float,
+ Format.R16G16B16A16Unorm => TextureFormat.R16G16B16A16Unorm,
+ Format.R16G16B16A16Snorm => TextureFormat.R16G16B16A16Snorm,
+ Format.R16G16B16A16Uint => TextureFormat.R16G16B16A16Uint,
+ Format.R16G16B16A16Sint => TextureFormat.R16G16B16A16Sint,
+ Format.R32G32B32A32Float => TextureFormat.R32G32B32A32Float,
+ Format.R32G32B32A32Uint => TextureFormat.R32G32B32A32Uint,
+ Format.R32G32B32A32Sint => TextureFormat.R32G32B32A32Sint,
+ Format.R10G10B10A2Unorm => TextureFormat.R10G10B10A2Unorm,
+ Format.R10G10B10A2Uint => TextureFormat.R10G10B10A2Uint,
+ Format.R11G11B10Float => TextureFormat.R11G11B10Float,
+ _ => TextureFormat.Unknown
+ };
+ }
+
+ ///
+ /// Queries texture target information.
+ ///
+ /// Texture handle
+ /// True if the texture is a buffer texture, false otherwise
+ public bool QueryIsTextureBuffer(int handle)
+ {
+ return GetTextureDescriptor(handle).UnpackTextureTarget() == TextureTarget.TextureBuffer;
+ }
+
+ ///
+ /// Queries texture target information.
+ ///
+ /// Texture handle
+ /// True if the texture is a rectangle texture, false otherwise
+ public bool QueryIsTextureRectangle(int handle)
+ {
+ var descriptor = GetTextureDescriptor(handle);
+
+ TextureTarget target = descriptor.UnpackTextureTarget();
+
+ bool is2DTexture = target == TextureTarget.Texture2D ||
+ target == TextureTarget.Texture2DRect;
+
+ return !descriptor.UnpackTextureCoordNormalized() && is2DTexture;
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Program.cs b/Ryujinx.Graphics.OpenGL/Program.cs
index 17e14df6db..d39e181d95 100644
--- a/Ryujinx.Graphics.OpenGL/Program.cs
+++ b/Ryujinx.Graphics.OpenGL/Program.cs
@@ -3,6 +3,7 @@ using Ryujinx.Common.Logging;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader.CodeGen.Glsl;
using System;
+using System.Buffers.Binary;
using System.Collections.Generic;
using System.Linq;
@@ -22,6 +23,8 @@ namespace Ryujinx.Graphics.OpenGL
{
Handle = GL.CreateProgram();
+ GL.ProgramParameter(Handle, ProgramParameterName.ProgramBinaryRetrievableHint, 1);
+
for (int index = 0; index < shaders.Length; index++)
{
int shaderHandle = ((Shader)shaders[index]).Handle;
@@ -93,6 +96,27 @@ namespace Ryujinx.Graphics.OpenGL
ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
}
+ public Program(ReadOnlySpan code)
+ {
+ BinaryFormat binaryFormat = (BinaryFormat)BinaryPrimitives.ReadInt32LittleEndian(code.Slice(code.Length - 4, 4));
+
+ Handle = GL.CreateProgram();
+
+ unsafe
+ {
+ fixed (byte* ptr = code)
+ {
+ GL.ProgramBinary(Handle, binaryFormat, (IntPtr)ptr, code.Length - 4);
+ }
+ }
+
+ CheckProgramLink();
+
+ FragmentIsBgraUniform = GL.GetUniformLocation(Handle, "is_bgra");
+ FragmentRenderScaleUniform = GL.GetUniformLocation(Handle, "fp_renderScale");
+ ComputeRenderScaleUniform = GL.GetUniformLocation(Handle, "cp_renderScale");
+ }
+
public void Bind()
{
GL.UseProgram(Handle);
@@ -113,6 +137,19 @@ namespace Ryujinx.Graphics.OpenGL
}
}
+ public byte[] GetBinary()
+ {
+ GL.GetProgram(Handle, (GetProgramParameterName)All.ProgramBinaryLength, out int size);
+
+ byte[] data = new byte[size + 4];
+
+ GL.GetProgramBinary(Handle, size, out _, out BinaryFormat binFormat, data);
+
+ BinaryPrimitives.WriteInt32LittleEndian(data.AsSpan().Slice(size, 4), (int)binFormat);
+
+ return data;
+ }
+
public void Dispose()
{
if (Handle != 0)
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 75bcda122a..f66acce3bc 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -165,5 +165,19 @@ namespace Ryujinx.Graphics.OpenGL
_window.Dispose();
_counters.Dispose();
}
+
+ public IProgram LoadProgramBinary(byte[] programBinary)
+ {
+ Program program = new Program(programBinary);
+
+ if (program.IsLinked)
+ {
+ return program;
+ }
+
+ program.Dispose();
+
+ return null;
+ }
}
}
diff --git a/Ryujinx.Graphics.Shader/BufferDescriptor.cs b/Ryujinx.Graphics.Shader/BufferDescriptor.cs
index 99855518f2..53a4fb164f 100644
--- a/Ryujinx.Graphics.Shader/BufferDescriptor.cs
+++ b/Ryujinx.Graphics.Shader/BufferDescriptor.cs
@@ -2,8 +2,8 @@ namespace Ryujinx.Graphics.Shader
{
public struct BufferDescriptor
{
- public int Binding { get; }
- public int Slot { get; }
+ public readonly int Binding;
+ public readonly int Slot;
public BufferDescriptor(int binding, int slot)
{
diff --git a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs
index 55d1225aeb..eb83544954 100644
--- a/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/IOpCodeTexture.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader.Decoders
int ComponentMask { get; }
- int Immediate { get; }
+ int HandleOffset { get; }
TextureLodMode LodMode { get; }
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs
index 5b2f806369..36f0b16452 100644
--- a/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeImage.cs
@@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.Instructions;
namespace Ryujinx.Graphics.Shader.Decoders
{
- class OpCodeImage : OpCode
+ class OpCodeImage : OpCodeTextureBase
{
public Register Ra { get; }
public Register Rb { get; }
@@ -15,8 +15,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
public ImageDimensions Dimensions { get; }
- public int Immediate { get; }
-
public bool UseComponents { get; }
public bool IsBindless { get; }
@@ -43,7 +41,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
Dimensions = (ImageDimensions)opCode.Extract(33, 3);
- Immediate = opCode.Extract(36, 13);
IsBindless = !opCode.Extract(51);
}
}
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs
index f19f7dadef..55e66f41d5 100644
--- a/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTexture.cs
@@ -2,7 +2,7 @@ using Ryujinx.Graphics.Shader.Instructions;
namespace Ryujinx.Graphics.Shader.Decoders
{
- class OpCodeTexture : OpCode, IOpCodeTexture
+ class OpCodeTexture : OpCodeTextureBase, IOpCodeTexture
{
public Register Rd { get; }
public Register Ra { get; }
@@ -14,8 +14,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
public int ComponentMask { get; }
- public int Immediate { get; }
-
public TextureLodMode LodMode { get; protected set; }
public bool HasOffset { get; protected set; }
@@ -36,8 +34,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
ComponentMask = opCode.Extract(31, 4);
- Immediate = opCode.Extract(36, 13);
-
LodMode = (TextureLodMode)opCode.Extract(55, 3);
}
}
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureBase.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureBase.cs
new file mode 100644
index 0000000000..0da1ca7651
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureBase.cs
@@ -0,0 +1,14 @@
+using Ryujinx.Graphics.Shader.Instructions;
+
+namespace Ryujinx.Graphics.Shader.Decoders
+{
+ class OpCodeTextureBase : OpCode
+ {
+ public int HandleOffset { get; }
+
+ public OpCodeTextureBase(InstEmitter emitter, ulong address, long opCode) : base(emitter, address, opCode)
+ {
+ HandleOffset = opCode.Extract(36, 13);
+ }
+ }
+}
diff --git a/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs
index 3ccd185cfb..be33a6f0c9 100644
--- a/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs
+++ b/Ryujinx.Graphics.Shader/Decoders/OpCodeTextureScalar.cs
@@ -3,7 +3,7 @@ using Ryujinx.Graphics.Shader.Instructions;
namespace Ryujinx.Graphics.Shader.Decoders
{
- class OpCodeTextureScalar : OpCode
+ class OpCodeTextureScalar : OpCodeTextureBase
{
#region "Component mask LUT"
private const int ____ = 0x0;
@@ -33,8 +33,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
public Register Rb { get; }
public Register Rd1 { get; }
- public int Immediate { get; }
-
public int ComponentMask { get; protected set; }
protected int RawType;
@@ -50,8 +48,6 @@ namespace Ryujinx.Graphics.Shader.Decoders
Rb = new Register(opCode.Extract(20, 8), RegisterType.Gpr);
Rd1 = new Register(opCode.Extract(28, 8), RegisterType.Gpr);
- Immediate = opCode.Extract(36, 13);
-
int compSel = opCode.Extract(50, 3);
RawType = opCode.Extract(53, 4);
diff --git a/Ryujinx.Graphics.Shader/InputTopology.cs b/Ryujinx.Graphics.Shader/InputTopology.cs
index 3b0dda45f1..429aa21194 100644
--- a/Ryujinx.Graphics.Shader/InputTopology.cs
+++ b/Ryujinx.Graphics.Shader/InputTopology.cs
@@ -1,6 +1,6 @@
namespace Ryujinx.Graphics.Shader
{
- public enum InputTopology
+ public enum InputTopology : byte
{
Points,
Lines,
diff --git a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
index 6b439901ee..a39062946b 100644
--- a/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
+++ b/Ryujinx.Graphics.Shader/Instructions/InstEmitTexture.cs
@@ -73,7 +73,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] sources = sourcesList.ToArray();
- int handle = !op.IsBindless ? op.Immediate : 0;
+ int handle = !op.IsBindless ? op.HandleOffset : 0;
TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None;
@@ -238,7 +238,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
if (!op.IsBindless)
{
- format = context.Config.GetTextureFormat(op.Immediate);
+ format = context.Config.GetTextureFormat(op.HandleOffset);
}
}
else
@@ -262,7 +262,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
Operand[] sources = sourcesList.ToArray();
- int handle = !op.IsBindless ? op.Immediate : 0;
+ int handle = !op.IsBindless ? op.HandleOffset : 0;
TextureFlags flags = op.IsBindless ? TextureFlags.Bindless : TextureFlags.None;
@@ -458,7 +458,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
flags = ConvertTextureFlags(tldsOp.Target) | TextureFlags.IntCoords;
- if (tldsOp.Target == TexelLoadTarget.Texture1DLodZero && context.Config.GpuAccessor.QueryIsTextureBuffer(tldsOp.Immediate))
+ if (tldsOp.Target == TexelLoadTarget.Texture1DLodZero && context.Config.GpuAccessor.QueryIsTextureBuffer(tldsOp.HandleOffset))
{
type = SamplerType.TextureBuffer;
flags &= ~TextureFlags.LodLevel;
@@ -607,7 +607,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
}
}
- int handle = op.Immediate;
+ int handle = op.HandleOffset;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
@@ -756,7 +756,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(rdIndex++, RegisterType.Gpr);
}
- int handle = op.Immediate;
+ int handle = op.HandleOffset;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
@@ -870,7 +870,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(rdIndex++, RegisterType.Gpr);
}
- int handle = !isBindless ? op.Immediate : 0;
+ int handle = !isBindless ? op.HandleOffset : 0;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
@@ -1019,7 +1019,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(rdIndex++, RegisterType.Gpr);
}
- int handle = !op.IsBindless ? op.Immediate : 0;
+ int handle = !op.IsBindless ? op.HandleOffset : 0;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
@@ -1104,7 +1104,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(rdIndex++, RegisterType.Gpr);
}
- int handle = !bindless ? op.Immediate : 0;
+ int handle = !bindless ? op.HandleOffset : 0;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
@@ -1181,7 +1181,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
{
// For bindless, we don't have any way to know the texture type,
// so we assume it's texture buffer when the sampler type is 1D, since that's more common.
- bool isTypeBuffer = isBindless || context.Config.GpuAccessor.QueryIsTextureBuffer(op.Immediate);
+ bool isTypeBuffer = isBindless || context.Config.GpuAccessor.QueryIsTextureBuffer(op.HandleOffset);
if (isTypeBuffer)
{
@@ -1269,7 +1269,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
return Register(rdIndex++, RegisterType.Gpr);
}
- int handle = !isBindless ? op.Immediate : 0;
+ int handle = !isBindless ? op.HandleOffset : 0;
for (int compMask = op.ComponentMask, compIndex = 0; compMask != 0; compMask >>= 1, compIndex++)
{
diff --git a/Ryujinx.Graphics.Shader/ShaderProgram.cs b/Ryujinx.Graphics.Shader/ShaderProgram.cs
index 30f4017535..0e037fc9c0 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgram.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgram.cs
@@ -4,8 +4,6 @@ namespace Ryujinx.Graphics.Shader
{
public class ShaderProgram
{
- public ShaderProgramInfo Info { get; }
-
public ShaderStage Stage { get; }
public string Code { get; private set; }
@@ -13,9 +11,8 @@ namespace Ryujinx.Graphics.Shader
public int SizeA { get; }
public int Size { get; }
- internal ShaderProgram(ShaderProgramInfo info, ShaderStage stage, string code, int size, int sizeA)
+ public ShaderProgram(ShaderStage stage, string code, int size, int sizeA)
{
- Info = info;
Stage = stage;
Code = code;
SizeA = sizeA;
diff --git a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
index d91c96132d..2324fac22c 100644
--- a/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
+++ b/Ryujinx.Graphics.Shader/ShaderProgramInfo.cs
@@ -12,7 +12,7 @@ namespace Ryujinx.Graphics.Shader
public bool UsesInstanceId { get; }
- internal ShaderProgramInfo(
+ public ShaderProgramInfo(
BufferDescriptor[] cBuffers,
BufferDescriptor[] sBuffers,
TextureDescriptor[] textures,
diff --git a/Ryujinx.Graphics.Shader/ShaderStage.cs b/Ryujinx.Graphics.Shader/ShaderStage.cs
index 30b65348e6..63e3b0684d 100644
--- a/Ryujinx.Graphics.Shader/ShaderStage.cs
+++ b/Ryujinx.Graphics.Shader/ShaderStage.cs
@@ -1,12 +1,14 @@
namespace Ryujinx.Graphics.Shader
{
- public enum ShaderStage
+ public enum ShaderStage : byte
{
Compute,
Vertex,
TessellationControl,
TessellationEvaluation,
Geometry,
- Fragment
+ Fragment,
+
+ Count
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/TextureDescriptor.cs b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
index 95f5e01dfc..b7b0ae12c5 100644
--- a/Ryujinx.Graphics.Shader/TextureDescriptor.cs
+++ b/Ryujinx.Graphics.Shader/TextureDescriptor.cs
@@ -2,15 +2,15 @@ namespace Ryujinx.Graphics.Shader
{
public struct TextureDescriptor
{
- public int Binding { get; }
+ public readonly int Binding;
- public SamplerType Type { get; }
- public TextureFormat Format { get; }
+ public readonly SamplerType Type;
+ public readonly TextureFormat Format;
- public int CbufSlot { get; }
- public int HandleIndex { get; }
+ public readonly int CbufSlot;
+ public readonly int HandleIndex;
- public TextureUsageFlags Flags { get; set; }
+ public TextureUsageFlags Flags;
public TextureDescriptor(int binding, SamplerType type, TextureFormat format, int cbufSlot, int handleIndex)
{
diff --git a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
index 08e7df3b1a..637ce8fe0f 100644
--- a/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
+++ b/Ryujinx.Graphics.Shader/Translation/ShaderConfig.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+
namespace Ryujinx.Graphics.Shader.Translation
{
class ShaderConfig
@@ -26,38 +28,42 @@ namespace Ryujinx.Graphics.Shader.Translation
public FeatureFlags UsedFeatures { get; private set; }
+ public HashSet TextureHandlesForCache { get; }
+
public ShaderConfig(IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts)
{
- Stage = ShaderStage.Compute;
- OutputTopology = OutputTopology.PointList;
- MaxOutputVertices = 0;
- LocalMemorySize = 0;
- ImapTypes = null;
- OmapTargets = null;
- OmapSampleMask = false;
- OmapDepth = false;
- GpuAccessor = gpuAccessor;
- Flags = flags;
- Size = 0;
- UsedFeatures = FeatureFlags.None;
- Counts = counts;
+ Stage = ShaderStage.Compute;
+ OutputTopology = OutputTopology.PointList;
+ MaxOutputVertices = 0;
+ LocalMemorySize = 0;
+ ImapTypes = null;
+ OmapTargets = null;
+ OmapSampleMask = false;
+ OmapDepth = false;
+ GpuAccessor = gpuAccessor;
+ Flags = flags;
+ Size = 0;
+ UsedFeatures = FeatureFlags.None;
+ Counts = counts;
+ TextureHandlesForCache = new HashSet();
}
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationFlags flags, TranslationCounts counts)
{
- Stage = header.Stage;
- OutputTopology = header.OutputTopology;
- MaxOutputVertices = header.MaxOutputVertexCount;
- LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize;
- ImapTypes = header.ImapTypes;
- OmapTargets = header.OmapTargets;
- OmapSampleMask = header.OmapSampleMask;
- OmapDepth = header.OmapDepth;
- GpuAccessor = gpuAccessor;
- Flags = flags;
- Size = 0;
- UsedFeatures = FeatureFlags.None;
- Counts = counts;
+ Stage = header.Stage;
+ OutputTopology = header.OutputTopology;
+ MaxOutputVertices = header.MaxOutputVertexCount;
+ LocalMemorySize = header.ShaderLocalMemoryLowSize + header.ShaderLocalMemoryHighSize;
+ ImapTypes = header.ImapTypes;
+ OmapTargets = header.OmapTargets;
+ OmapSampleMask = header.OmapSampleMask;
+ OmapDepth = header.OmapDepth;
+ GpuAccessor = gpuAccessor;
+ Flags = flags;
+ Size = 0;
+ UsedFeatures = FeatureFlags.None;
+ Counts = counts;
+ TextureHandlesForCache = new HashSet();
}
public int GetDepthRegister()
diff --git a/Ryujinx.Graphics.Shader/Translation/Translator.cs b/Ryujinx.Graphics.Shader/Translation/Translator.cs
index 3485b5ed9d..85a46d1960 100644
--- a/Ryujinx.Graphics.Shader/Translation/Translator.cs
+++ b/Ryujinx.Graphics.Shader/Translation/Translator.cs
@@ -3,7 +3,6 @@ using Ryujinx.Graphics.Shader.Decoders;
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.StructuredIr;
using Ryujinx.Graphics.Shader.Translation.Optimizations;
-using System;
using System.Collections.Generic;
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
@@ -14,7 +13,7 @@ namespace Ryujinx.Graphics.Shader.Translation
{
private const int HeaderSize = 0x50;
- private struct FunctionCode
+ internal struct FunctionCode
{
public Operation[] Code { get; }
@@ -24,7 +23,7 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
- public static ShaderProgram Translate(
+ public static TranslatorContext CreateContext(
ulong address,
IGpuAccessor gpuAccessor,
TranslationFlags flags,
@@ -32,10 +31,12 @@ namespace Ryujinx.Graphics.Shader.Translation
{
counts ??= new TranslationCounts();
- return Translate(DecodeShader(address, gpuAccessor, flags, counts, out ShaderConfig config), config);
+ Block[][] cfg = DecodeShader(address, gpuAccessor, flags, counts, out ShaderConfig config);
+
+ return new TranslatorContext(address, cfg, config);
}
- public static ShaderProgram Translate(
+ public static TranslatorContext CreateContext(
ulong addressA,
ulong addressB,
IGpuAccessor gpuAccessor,
@@ -44,15 +45,13 @@ namespace Ryujinx.Graphics.Shader.Translation
{
counts ??= new TranslationCounts();
- FunctionCode[] funcA = DecodeShader(addressA, gpuAccessor, flags | TranslationFlags.VertexA, counts, out ShaderConfig configA);
- FunctionCode[] funcB = DecodeShader(addressB, gpuAccessor, flags, counts, out ShaderConfig config);
+ Block[][] cfgA = DecodeShader(addressA, gpuAccessor, flags | TranslationFlags.VertexA, counts, out ShaderConfig configA);
+ Block[][] cfgB = DecodeShader(addressB, gpuAccessor, flags, counts, out ShaderConfig configB);
- config.SetUsedFeature(configA.UsedFeatures);
-
- return Translate(Combine(funcA, funcB), config, configA.Size);
+ return new TranslatorContext(addressA, addressB, cfgA, cfgB, configA, configB);
}
- private static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config, int sizeA = 0)
+ internal static ShaderProgram Translate(FunctionCode[] functions, ShaderConfig config, out ShaderProgramInfo shaderProgramInfo, int sizeA = 0)
{
var cfgs = new ControlFlowGraph[functions.Length];
var frus = new RegisterUsage.FunctionRegisterUsage[functions.Length];
@@ -106,7 +105,7 @@ namespace Ryujinx.Graphics.Shader.Translation
GlslProgram program = GlslGenerator.Generate(sInfo, config);
- ShaderProgramInfo spInfo = new ShaderProgramInfo(
+ shaderProgramInfo = new ShaderProgramInfo(
program.CBufferDescriptors,
program.SBufferDescriptors,
program.TextureDescriptors,
@@ -115,10 +114,10 @@ namespace Ryujinx.Graphics.Shader.Translation
string glslCode = program.Code;
- return new ShaderProgram(spInfo, config.Stage, glslCode, config.Size, sizeA);
+ return new ShaderProgram(config.Stage, glslCode, config.Size, sizeA);
}
- private static FunctionCode[] DecodeShader(
+ private static Block[][] DecodeShader(
ulong address,
IGpuAccessor gpuAccessor,
TranslationFlags flags,
@@ -126,6 +125,7 @@ namespace Ryujinx.Graphics.Shader.Translation
out ShaderConfig config)
{
Block[][] cfg;
+ ulong maxEndAddress = 0;
if ((flags & TranslationFlags.Compute) != 0)
{
@@ -140,13 +140,34 @@ namespace Ryujinx.Graphics.Shader.Translation
cfg = Decoder.Decode(gpuAccessor, address + HeaderSize);
}
- if (cfg == null)
+ for (int funcIndex = 0; funcIndex < cfg.Length; funcIndex++)
{
- gpuAccessor.Log("Invalid branch detected, failed to build CFG.");
+ for (int blkIndex = 0; blkIndex < cfg[funcIndex].Length; blkIndex++)
+ {
+ Block block = cfg[funcIndex][blkIndex];
- return Array.Empty();
+ if (maxEndAddress < block.EndAddress)
+ {
+ maxEndAddress = block.EndAddress;
+ }
+
+ for (int index = 0; index < block.OpCodes.Count; index++)
+ {
+ if (block.OpCodes[index] is OpCodeTextureBase texture)
+ {
+ config.TextureHandlesForCache.Add(texture.HandleOffset);
+ }
+ }
+ }
}
+ config.SizeAdd((int)maxEndAddress + (flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize));
+
+ return cfg;
+ }
+
+ internal static FunctionCode[] EmitShader(Block[][] cfg, ShaderConfig config)
+ {
Dictionary funcIds = new Dictionary();
for (int funcIndex = 0; funcIndex < cfg.Length; funcIndex++)
@@ -156,8 +177,6 @@ namespace Ryujinx.Graphics.Shader.Translation
List funcs = new List();
- ulong maxEndAddress = 0;
-
for (int funcIndex = 0; funcIndex < cfg.Length; funcIndex++)
{
EmitterContext context = new EmitterContext(config, funcIndex != 0, funcIds);
@@ -166,11 +185,6 @@ namespace Ryujinx.Graphics.Shader.Translation
{
Block block = cfg[funcIndex][blkIndex];
- if (maxEndAddress < block.EndAddress)
- {
- maxEndAddress = block.EndAddress;
- }
-
context.CurrBlock = block;
context.MarkLabel(context.GetLabel(block.Address));
@@ -181,12 +195,10 @@ namespace Ryujinx.Graphics.Shader.Translation
funcs.Add(new FunctionCode(context.GetOperations()));
}
- config.SizeAdd((int)maxEndAddress + (flags.HasFlag(TranslationFlags.Compute) ? 0 : HeaderSize));
-
return funcs.ToArray();
}
- internal static void EmitOps(EmitterContext context, Block block)
+ private static void EmitOps(EmitterContext context, Block block)
{
for (int opIndex = 0; opIndex < block.OpCodes.Count; opIndex++)
{
@@ -267,101 +279,5 @@ namespace Ryujinx.Graphics.Shader.Translation
}
}
}
-
- private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b)
- {
- // Here we combine two shaders.
- // For shader A:
- // - All user attribute stores on shader A are turned into copies to a
- // temporary variable. It's assumed that shader B will consume them.
- // - All return instructions are turned into branch instructions, the
- // branch target being the start of the shader B code.
- // For shader B:
- // - All user attribute loads on shader B are turned into copies from a
- // temporary variable, as long that attribute is written by shader A.
- FunctionCode[] output = new FunctionCode[a.Length + b.Length - 1];
-
- List ops = new List(a.Length + b.Length);
-
- Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4];
-
- Operand lblB = Label();
-
- for (int index = 0; index < a[0].Code.Length; index++)
- {
- Operation operation = a[0].Code[index];
-
- if (IsUserAttribute(operation.Dest))
- {
- int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
-
- Operand temp = temps[tIndex];
-
- if (temp == null)
- {
- temp = Local();
-
- temps[tIndex] = temp;
- }
-
- operation.Dest = temp;
- }
-
- if (operation.Inst == Instruction.Return)
- {
- ops.Add(new Operation(Instruction.Branch, lblB));
- }
- else
- {
- ops.Add(operation);
- }
- }
-
- ops.Add(new Operation(Instruction.MarkLabel, lblB));
-
- for (int index = 0; index < b[0].Code.Length; index++)
- {
- Operation operation = b[0].Code[index];
-
- for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
- {
- Operand src = operation.GetSource(srcIndex);
-
- if (IsUserAttribute(src))
- {
- Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
-
- if (temp != null)
- {
- operation.SetSource(srcIndex, temp);
- }
- }
- }
-
- ops.Add(operation);
- }
-
- output[0] = new FunctionCode(ops.ToArray());
-
- for (int i = 1; i < a.Length; i++)
- {
- output[i] = a[i];
- }
-
- for (int i = 1; i < b.Length; i++)
- {
- output[a.Length + i - 1] = b[i];
- }
-
- return output;
- }
-
- private static bool IsUserAttribute(Operand operand)
- {
- return operand != null &&
- operand.Type == OperandType.Attribute &&
- operand.Value >= AttributeConsts.UserAttributeBase &&
- operand.Value < AttributeConsts.UserAttributeEnd;
- }
}
}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
new file mode 100644
index 0000000000..3092e07769
--- /dev/null
+++ b/Ryujinx.Graphics.Shader/Translation/TranslatorContext.cs
@@ -0,0 +1,160 @@
+using Ryujinx.Graphics.Shader.Decoders;
+using Ryujinx.Graphics.Shader.IntermediateRepresentation;
+using System.Collections.Generic;
+
+using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
+using static Ryujinx.Graphics.Shader.Translation.Translator;
+
+namespace Ryujinx.Graphics.Shader.Translation
+{
+ public class TranslatorContext
+ {
+ private readonly Block[][] _cfg;
+ private readonly Block[][] _cfgA;
+ private ShaderConfig _config;
+ private ShaderConfig _configA;
+
+ public ulong Address { get; }
+ public ulong AddressA { get; }
+
+ public ShaderStage Stage => _config.Stage;
+ public int Size => _config.Size;
+ public int SizeA => _configA != null ? _configA.Size : 0;
+
+ public HashSet TextureHandlesForCache => _config.TextureHandlesForCache;
+
+ public IGpuAccessor GpuAccessor => _config.GpuAccessor;
+
+ internal TranslatorContext(ulong address, Block[][] cfg, ShaderConfig config)
+ {
+ Address = address;
+ AddressA = 0;
+ _config = config;
+ _configA = null;
+ _cfg = cfg;
+ _cfgA = null;
+ }
+
+ internal TranslatorContext(ulong addressA, ulong addressB, Block[][] cfgA, Block[][] cfgB, ShaderConfig configA, ShaderConfig configB)
+ {
+ Address = addressB;
+ AddressA = addressA;
+ _config = configB;
+ _configA = configA;
+ _cfg = cfgB;
+ _cfgA = cfgA;
+ }
+
+ private static bool IsUserAttribute(Operand operand)
+ {
+ return operand != null &&
+ operand.Type == OperandType.Attribute &&
+ operand.Value >= AttributeConsts.UserAttributeBase &&
+ operand.Value < AttributeConsts.UserAttributeEnd;
+ }
+
+ private static FunctionCode[] Combine(FunctionCode[] a, FunctionCode[] b)
+ {
+ // Here we combine two shaders.
+ // For shader A:
+ // - All user attribute stores on shader A are turned into copies to a
+ // temporary variable. It's assumed that shader B will consume them.
+ // - All return instructions are turned into branch instructions, the
+ // branch target being the start of the shader B code.
+ // For shader B:
+ // - All user attribute loads on shader B are turned into copies from a
+ // temporary variable, as long that attribute is written by shader A.
+ FunctionCode[] output = new FunctionCode[a.Length + b.Length - 1];
+
+ List ops = new List(a.Length + b.Length);
+
+ Operand[] temps = new Operand[AttributeConsts.UserAttributesCount * 4];
+
+ Operand lblB = Label();
+
+ for (int index = 0; index < a[0].Code.Length; index++)
+ {
+ Operation operation = a[0].Code[index];
+
+ if (IsUserAttribute(operation.Dest))
+ {
+ int tIndex = (operation.Dest.Value - AttributeConsts.UserAttributeBase) / 4;
+
+ Operand temp = temps[tIndex];
+
+ if (temp == null)
+ {
+ temp = Local();
+
+ temps[tIndex] = temp;
+ }
+
+ operation.Dest = temp;
+ }
+
+ if (operation.Inst == Instruction.Return)
+ {
+ ops.Add(new Operation(Instruction.Branch, lblB));
+ }
+ else
+ {
+ ops.Add(operation);
+ }
+ }
+
+ ops.Add(new Operation(Instruction.MarkLabel, lblB));
+
+ for (int index = 0; index < b[0].Code.Length; index++)
+ {
+ Operation operation = b[0].Code[index];
+
+ for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
+ {
+ Operand src = operation.GetSource(srcIndex);
+
+ if (IsUserAttribute(src))
+ {
+ Operand temp = temps[(src.Value - AttributeConsts.UserAttributeBase) / 4];
+
+ if (temp != null)
+ {
+ operation.SetSource(srcIndex, temp);
+ }
+ }
+ }
+
+ ops.Add(operation);
+ }
+
+ output[0] = new FunctionCode(ops.ToArray());
+
+ for (int i = 1; i < a.Length; i++)
+ {
+ output[i] = a[i];
+ }
+
+ for (int i = 1; i < b.Length; i++)
+ {
+ output[a.Length + i - 1] = b[i];
+ }
+
+ return output;
+ }
+
+ public ShaderProgram Translate(out ShaderProgramInfo shaderProgramInfo)
+ {
+ FunctionCode[] code = EmitShader(_cfg, _config);
+
+ if (_configA != null)
+ {
+ FunctionCode[] codeA = EmitShader(_cfgA, _configA);
+
+ _config.SetUsedFeature(_configA.UsedFeatures);
+
+ code = Combine(codeA, code);
+ }
+
+ return Translator.Translate(code, _config, out shaderProgramInfo, SizeA);
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/ApplicationLoader.cs b/Ryujinx.HLE/HOS/ApplicationLoader.cs
index 46b621783d..96fb3a1f85 100644
--- a/Ryujinx.HLE/HOS/ApplicationLoader.cs
+++ b/Ryujinx.HLE/HOS/ApplicationLoader.cs
@@ -498,8 +498,13 @@ namespace Ryujinx.HLE.HOS
Logger.Warning?.Print(LogClass.Ptc, $"Detected exefs modifications. PPTC disabled.");
}
+ Graphics.Gpu.GraphicsConfig.TitleId = TitleIdText;
+ _device.Gpu.HostInitalized.Set();
+
Ptc.Initialize(TitleIdText, DisplayVersion, _device.System.EnablePtc && !modified);
+ _device.Gpu.ReadyEvent.WaitOne();
+
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: programs);
}
@@ -595,6 +600,12 @@ namespace Ryujinx.HLE.HOS
TitleId = metaData.Aci0.TitleId;
TitleIs64Bit = metaData.Is64Bit;
+ // Explicitly null titleid to disable the shader cache
+ Graphics.Gpu.GraphicsConfig.TitleId = null;
+
+ _device.Gpu.HostInitalized.Set();
+ _device.Gpu.ReadyEvent.WaitOne();
+
ProgramLoader.LoadNsos(_device.System.KernelContext, metaData, executables: executable);
}
diff --git a/Ryujinx.ShaderTools/Program.cs b/Ryujinx.ShaderTools/Program.cs
index 567083e485..39b9425cf7 100644
--- a/Ryujinx.ShaderTools/Program.cs
+++ b/Ryujinx.ShaderTools/Program.cs
@@ -36,7 +36,7 @@ namespace Ryujinx.ShaderTools
byte[] data = File.ReadAllBytes(args[^1]);
- string code = Translator.Translate(0, new GpuAccessor(data), flags).Code;
+ string code = Translator.CreateContext(0, new GpuAccessor(data), flags).Translate(out _).Code;
Console.WriteLine(code);
}
diff --git a/Ryujinx/Config.json b/Ryujinx/Config.json
index fdbd9c1aff..6c6f9befa6 100644
--- a/Ryujinx/Config.json
+++ b/Ryujinx/Config.json
@@ -21,6 +21,7 @@
"enable_discord_integration": true,
"check_updates_on_start": true,
"enable_vsync": true,
+ "enable_shader_cache": true,
"enable_multicore_scheduling": true,
"enable_ptc": false,
"enable_fs_integrity_checks": true,
diff --git a/Ryujinx/Ui/GLRenderer.cs b/Ryujinx/Ui/GLRenderer.cs
index b635ad1c54..0e5ef8f23c 100644
--- a/Ryujinx/Ui/GLRenderer.cs
+++ b/Ryujinx/Ui/GLRenderer.cs
@@ -199,19 +199,6 @@ namespace Ryujinx.Ui
Gtk.Application.Invoke(delegate
{
parent.Present();
-
- string titleNameSection = string.IsNullOrWhiteSpace(_device.Application.TitleName) ? string.Empty
- : $" - {_device.Application.TitleName}";
-
- string titleVersionSection = string.IsNullOrWhiteSpace(_device.Application.DisplayVersion) ? string.Empty
- : $" v{_device.Application.DisplayVersion}";
-
- string titleIdSection = string.IsNullOrWhiteSpace(_device.Application.TitleIdText) ? string.Empty
- : $" ({_device.Application.TitleIdText.ToUpper()})";
-
- string titleArchSection = _device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
-
- parent.Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
});
Thread renderLoopThread = new Thread(Render)
@@ -313,7 +300,7 @@ namespace Ryujinx.Ui
{
if (!(_device.Gpu.Renderer is Renderer))
{
- throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using GLRenderer!");
+ throw new NotSupportedException($"GPU renderer must be an OpenGL renderer when using {typeof(Renderer).Name}!");
}
_renderer = (Renderer)_device.Gpu.Renderer;
@@ -327,7 +314,7 @@ namespace Ryujinx.Ui
parent.Present();
GraphicsContext.MakeCurrent(WindowInfo);
- _renderer.Initialize(_glLogLevel);
+ _device.Gpu.Initialize(_glLogLevel);
// Make sure the first frame is not transparent.
GL.ClearColor(OpenTK.Color.Black);
diff --git a/Ryujinx/Ui/GameTableContextMenu.cs b/Ryujinx/Ui/GameTableContextMenu.cs
index 61e6a80c95..58c4079145 100644
--- a/Ryujinx/Ui/GameTableContextMenu.cs
+++ b/Ryujinx/Ui/GameTableContextMenu.cs
@@ -111,22 +111,34 @@ namespace Ryujinx.Ui
MenuItem managePtcMenu = new MenuItem("Cache Management");
- MenuItem purgePtcCache = new MenuItem("Purge PPTC cache")
+ MenuItem purgePtcCache = new MenuItem("Purge PPTC Cache")
{
TooltipText = "Delete the Application's PPTC cache."
};
-
- MenuItem openPtcDir = new MenuItem("Open PPTC directory")
+
+ MenuItem purgeShaderCache = new MenuItem("Purge Shader Cache")
{
- TooltipText = "Open the directory which contains Application's PPTC cache."
+ TooltipText = "Delete the Application's shader cache."
};
+
+ MenuItem openPtcDir = new MenuItem("Open PPTC Directory")
+ {
+ TooltipText = "Open the directory which contains the Application's PPTC cache."
+ };
+
+ MenuItem openShaderCacheDir = new MenuItem("Open Shader Cache Directory")
+ {
+ TooltipText = "Open the directory which contains the Application's shader cache."
+ };
+
+ Menu manageSubMenu = new Menu();
- Menu managePtcSubMenu = new Menu();
+ manageSubMenu.Append(purgePtcCache);
+ manageSubMenu.Append(purgeShaderCache);
+ manageSubMenu.Append(openPtcDir);
+ manageSubMenu.Append(openShaderCacheDir);
- managePtcSubMenu.Append(purgePtcCache);
- managePtcSubMenu.Append(openPtcDir);
-
- managePtcMenu.Submenu = managePtcSubMenu;
+ managePtcMenu.Submenu = manageSubMenu;
openSaveUserDir.Activated += OpenSaveUserDir_Clicked;
openSaveDeviceDir.Activated += OpenSaveDeviceDir_Clicked;
@@ -138,8 +150,10 @@ namespace Ryujinx.Ui
extractExeFs.Activated += ExtractExeFs_Clicked;
extractLogo.Activated += ExtractLogo_Clicked;
purgePtcCache.Activated += PurgePtcCache_Clicked;
+ purgeShaderCache.Activated += PurgeShaderCache_Clicked;
openPtcDir.Activated += OpenPtcDir_Clicked;
-
+ openShaderCacheDir.Activated += OpenShaderCacheDir_Clicked;
+
this.Add(openSaveUserDir);
this.Add(openSaveDeviceDir);
this.Add(openSaveBcatDir);
@@ -640,6 +654,24 @@ namespace Ryujinx.Ui
Verb = "open"
});
}
+
+ private void OpenShaderCacheDir_Clicked(object sender, EventArgs args)
+ {
+ string titleId = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n")[1].ToLower();
+ string shaderCacheDir = System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader");
+
+ if (!Directory.Exists(shaderCacheDir))
+ {
+ Directory.CreateDirectory(shaderCacheDir);
+ }
+
+ Process.Start(new ProcessStartInfo
+ {
+ FileName = shaderCacheDir,
+ UseShellExecute = true,
+ Verb = "open"
+ });
+ }
private void PurgePtcCache_Clicked(object sender, EventArgs args)
{
@@ -678,5 +710,41 @@ namespace Ryujinx.Ui
warningDialog.Dispose();
}
+
+ private void PurgeShaderCache_Clicked(object sender, EventArgs args)
+ {
+ string[] tableEntry = _gameTableStore.GetValue(_rowIter, 2).ToString().Split("\n");
+ string titleId = tableEntry[1].ToLower();
+
+ DirectoryInfo shaderCacheDir = new DirectoryInfo(System.IO.Path.Combine(AppDataManager.GamesDirPath, titleId, "cache", "shader"));
+
+ MessageDialog warningDialog = new MessageDialog(null, DialogFlags.Modal, MessageType.Warning, ButtonsType.YesNo, null)
+ {
+ Title = "Ryujinx - Warning",
+ Text = $"You are about to delete the shader cache for '{tableEntry[0]}'. Are you sure you want to proceed?",
+ WindowPosition = WindowPosition.Center
+ };
+
+ List cacheDirectory = new List();
+
+ if (shaderCacheDir.Exists) { cacheDirectory.AddRange(shaderCacheDir.EnumerateDirectories("*")); }
+
+ if (cacheDirectory.Count > 0 && warningDialog.Run() == (int)ResponseType.Yes)
+ {
+ foreach (DirectoryInfo directory in cacheDirectory)
+ {
+ try
+ {
+ directory.Delete(true);
+ }
+ catch (Exception e)
+ {
+ Logger.Error?.Print(LogClass.Application, $"Error purging shader cache {directory.Name}: {e}");
+ }
+ }
+ }
+
+ warningDialog.Dispose();
+ }
}
}
diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs
index 10042143cc..17eaea7491 100644
--- a/Ryujinx/Ui/MainWindow.cs
+++ b/Ryujinx/Ui/MainWindow.cs
@@ -39,6 +39,7 @@ namespace Ryujinx.Ui
public static GlRenderer GlWidget => _glWidget;
private static AutoResetEvent _deviceExitStatus = new AutoResetEvent(false);
+ private static AutoResetEvent _widgetInitEvent = new AutoResetEvent(false);
private static ListStore _tableStore;
@@ -433,6 +434,30 @@ namespace Ryujinx.Ui
}
}
+ _widgetInitEvent.Reset();
+
+#if MACOS_BUILD
+ CreateGameWindow(device);
+#else
+ Thread windowThread = new Thread(() =>
+ {
+ CreateGameWindow(device);
+ })
+ {
+ Name = "GUI.WindowThread"
+ };
+
+ windowThread.Start();
+#endif
+
+ _widgetInitEvent.WaitOne();
+
+ // Make sure the widget get initialized by forcing an update of GTK
+ while (Application.EventsPending())
+ {
+ Application.RunIteration();
+ }
+
Logger.Notice.Print(LogClass.Application, $"Using Firmware Version: {firmwareVersion?.VersionString}");
if (Directory.Exists(path))
@@ -493,25 +518,24 @@ namespace Ryujinx.Ui
return;
}
+ string titleNameSection = string.IsNullOrWhiteSpace(device.Application.TitleName) ? string.Empty
+ : $" - {device.Application.TitleName}";
+
+ string titleVersionSection = string.IsNullOrWhiteSpace(device.Application.DisplayVersion) ? string.Empty
+ : $" v{device.Application.DisplayVersion}";
+
+ string titleIdSection = string.IsNullOrWhiteSpace(device.Application.TitleIdText) ? string.Empty
+ : $" ({device.Application.TitleIdText.ToUpper()})";
+
+ string titleArchSection = device.Application.TitleIs64Bit ? " (64-bit)" : " (32-bit)";
+
+ Title = $"Ryujinx {Program.Version}{titleNameSection}{titleVersionSection}{titleIdSection}{titleArchSection}";
+
_emulationContext = device;
_gamePath = path;
_deviceExitStatus.Reset();
-#if MACOS_BUILD
- CreateGameWindow(device);
-#else
- Thread windowThread = new Thread(() =>
- {
- CreateGameWindow(device);
- })
- {
- Name = "GUI.WindowThread"
- };
-
- windowThread.Start();
-#endif
-
_gameLoaded = true;
_stopEmulation.Sensitive = true;
@@ -534,7 +558,7 @@ namespace Ryujinx.Ui
_windowsMultimediaTimerResolution = new WindowsMultimediaTimerResolution(1);
}
- _glWidget = new GlRenderer(_emulationContext, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
+ _glWidget = new GlRenderer(device, ConfigurationState.Instance.Logger.GraphicsDebugLevel);
Application.Invoke(delegate
{
@@ -551,6 +575,8 @@ namespace Ryujinx.Ui
}
});
+ _widgetInitEvent.Set();
+
_glWidget.WaitEvent.WaitOne();
_glWidget.Start();
@@ -658,6 +684,7 @@ namespace Ryujinx.Ui
Graphics.Gpu.GraphicsConfig.ResScale = (resScale == -1) ? resScaleCustom : resScale;
Graphics.Gpu.GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
Graphics.Gpu.GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
+ Graphics.Gpu.GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
}
public static void SaveConfig()
diff --git a/Ryujinx/Ui/SettingsWindow.cs b/Ryujinx/Ui/SettingsWindow.cs
index bd4cbbca36..4646df06ac 100644
--- a/Ryujinx/Ui/SettingsWindow.cs
+++ b/Ryujinx/Ui/SettingsWindow.cs
@@ -42,6 +42,7 @@ namespace Ryujinx.Ui
[GUI] CheckButton _discordToggle;
[GUI] CheckButton _checkUpdatesToggle;
[GUI] CheckButton _vSyncToggle;
+ [GUI] CheckButton _shaderCacheToggle;
[GUI] CheckButton _multiSchedToggle;
[GUI] CheckButton _ptcToggle;
[GUI] CheckButton _fsicToggle;
@@ -182,6 +183,11 @@ namespace Ryujinx.Ui
_vSyncToggle.Click();
}
+ if (ConfigurationState.Instance.Graphics.EnableShaderCache)
+ {
+ _shaderCacheToggle.Click();
+ }
+
if (ConfigurationState.Instance.System.EnableMulticoreScheduling)
{
_multiSchedToggle.Click();
@@ -528,6 +534,7 @@ namespace Ryujinx.Ui
ConfigurationState.Instance.EnableDiscordIntegration.Value = _discordToggle.Active;
ConfigurationState.Instance.CheckUpdatesOnStart.Value = _checkUpdatesToggle.Active;
ConfigurationState.Instance.Graphics.EnableVsync.Value = _vSyncToggle.Active;
+ ConfigurationState.Instance.Graphics.EnableShaderCache.Value = _shaderCacheToggle.Active;
ConfigurationState.Instance.System.EnableMulticoreScheduling.Value = _multiSchedToggle.Active;
ConfigurationState.Instance.System.EnablePtc.Value = _ptcToggle.Active;
ConfigurationState.Instance.System.EnableFsIntegrityChecks.Value = _fsicToggle.Active;
diff --git a/Ryujinx/Ui/SettingsWindow.glade b/Ryujinx/Ui/SettingsWindow.glade
index 9a51ba2b65..ef2262df18 100644
--- a/Ryujinx/Ui/SettingsWindow.glade
+++ b/Ryujinx/Ui/SettingsWindow.glade
@@ -1701,6 +1701,24 @@
10
10
vertical
+
+
+
+ False
+ True
+ 0
+
+
@@ -1817,7 +1835,7 @@
False
True
- 1
+ 2