aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs282
1 files changed, 282 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
new file mode 100644
index 00000000..e35c06b1
--- /dev/null
+++ b/src/Ryujinx.Graphics.Gpu/Shader/ShaderCacheHashTable.cs
@@ -0,0 +1,282 @@
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.Shader.HashTable;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Shader
+{
+ /// <summary>
+ /// Holds already cached code for a guest shader.
+ /// </summary>
+ struct CachedGraphicsGuestCode
+ {
+ public byte[] VertexACode;
+ public byte[] VertexBCode;
+ public byte[] TessControlCode;
+ public byte[] TessEvaluationCode;
+ public byte[] GeometryCode;
+ public byte[] FragmentCode;
+
+ /// <summary>
+ /// Gets the guest code of a shader stage by its index.
+ /// </summary>
+ /// <param name="stageIndex">Index of the shader stage</param>
+ /// <returns>Guest code, or null if not present</returns>
+ public byte[] GetByIndex(int stageIndex)
+ {
+ return stageIndex switch
+ {
+ 1 => TessControlCode,
+ 2 => TessEvaluationCode,
+ 3 => GeometryCode,
+ 4 => FragmentCode,
+ _ => VertexBCode
+ };
+ }
+ }
+
+ /// <summary>
+ /// Graphics shader cache hash table.
+ /// </summary>
+ class ShaderCacheHashTable
+ {
+ /// <summary>
+ /// Shader ID cache.
+ /// </summary>
+ private struct IdCache
+ {
+ private PartitionedHashTable<int> _cache;
+ private int _id;
+
+ /// <summary>
+ /// Initializes the state.
+ /// </summary>
+ public void Initialize()
+ {
+ _cache = new PartitionedHashTable<int>();
+ _id = 0;
+ }
+
+ /// <summary>
+ /// Adds guest code to the cache.
+ /// </summary>
+ /// <remarks>
+ /// If the code was already cached, it will just return the existing ID.
+ /// </remarks>
+ /// <param name="code">Code to add</param>
+ /// <returns>Unique ID for the guest code</returns>
+ public int Add(byte[] code)
+ {
+ int id = ++_id;
+ int cachedId = _cache.GetOrAdd(code, id);
+ if (cachedId != id)
+ {
+ --_id;
+ }
+
+ return cachedId;
+ }
+
+ /// <summary>
+ /// Tries to find cached guest code.
+ /// </summary>
+ /// <param name="dataAccessor">Code accessor used to read guest code to find a match on the hash table</param>
+ /// <param name="id">ID of the guest code, if found</param>
+ /// <param name="data">Cached guest code, if found</param>
+ /// <returns>True if found, false otherwise</returns>
+ public bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
+ {
+ return _cache.TryFindItem(dataAccessor, out id, out data);
+ }
+ }
+
+ /// <summary>
+ /// Guest code IDs of the guest shaders that when combined forms a single host program.
+ /// </summary>
+ private struct IdTable : IEquatable<IdTable>
+ {
+ public int VertexAId;
+ public int VertexBId;
+ public int TessControlId;
+ public int TessEvaluationId;
+ public int GeometryId;
+ public int FragmentId;
+
+ public override bool Equals(object obj)
+ {
+ return obj is IdTable other && Equals(other);
+ }
+
+ public bool Equals(IdTable other)
+ {
+ return other.VertexAId == VertexAId &&
+ other.VertexBId == VertexBId &&
+ other.TessControlId == TessControlId &&
+ other.TessEvaluationId == TessEvaluationId &&
+ other.GeometryId == GeometryId &&
+ other.FragmentId == FragmentId;
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId);
+ }
+ }
+
+ private IdCache _vertexACache;
+ private IdCache _vertexBCache;
+ private IdCache _tessControlCache;
+ private IdCache _tessEvaluationCache;
+ private IdCache _geometryCache;
+ private IdCache _fragmentCache;
+
+ private readonly Dictionary<IdTable, ShaderSpecializationList> _shaderPrograms;
+
+ /// <summary>
+ /// Creates a new graphics shader cache hash table.
+ /// </summary>
+ public ShaderCacheHashTable()
+ {
+ _vertexACache.Initialize();
+ _vertexBCache.Initialize();
+ _tessControlCache.Initialize();
+ _tessEvaluationCache.Initialize();
+ _geometryCache.Initialize();
+ _fragmentCache.Initialize();
+
+ _shaderPrograms = new Dictionary<IdTable, ShaderSpecializationList>();
+ }
+
+ /// <summary>
+ /// Adds a program to the cache.
+ /// </summary>
+ /// <param name="program">Program to be added</param>
+ public void Add(CachedShaderProgram program)
+ {
+ IdTable idTable = new IdTable();
+
+ foreach (var shader in program.Shaders)
+ {
+ if (shader == null)
+ {
+ continue;
+ }
+
+ if (shader.Info != null)
+ {
+ switch (shader.Info.Stage)
+ {
+ case ShaderStage.Vertex:
+ idTable.VertexBId = _vertexBCache.Add(shader.Code);
+ break;
+ case ShaderStage.TessellationControl:
+ idTable.TessControlId = _tessControlCache.Add(shader.Code);
+ break;
+ case ShaderStage.TessellationEvaluation:
+ idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code);
+ break;
+ case ShaderStage.Geometry:
+ idTable.GeometryId = _geometryCache.Add(shader.Code);
+ break;
+ case ShaderStage.Fragment:
+ idTable.FragmentId = _fragmentCache.Add(shader.Code);
+ break;
+ }
+ }
+ else
+ {
+ idTable.VertexAId = _vertexACache.Add(shader.Code);
+ }
+ }
+
+ if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
+ {
+ specList = new ShaderSpecializationList();
+ _shaderPrograms.Add(idTable, specList);
+ }
+
+ specList.Add(program);
+ }
+
+ /// <summary>
+ /// Tries to find a cached program.
+ /// </summary>
+ /// <remarks>
+ /// Even if false is returned, <paramref name="guestCode"/> might still contain cached guest code.
+ /// This can be used to avoid additional allocations for guest code that was already cached.
+ /// </remarks>
+ /// <param name="channel">GPU channel</param>
+ /// <param name="poolState">Texture pool state</param>
+ /// <param name="graphicsState">Graphics state</param>
+ /// <param name="addresses">Guest addresses of the shaders to find</param>
+ /// <param name="program">Cached host program for the given state, if found</param>
+ /// <param name="guestCode">Cached guest code, if any found</param>
+ /// <returns>True if a cached host program was found, false otherwise</returns>
+ public bool TryFind(
+ GpuChannel channel,
+ ref GpuChannelPoolState poolState,
+ ref GpuChannelGraphicsState graphicsState,
+ ShaderAddresses addresses,
+ out CachedShaderProgram program,
+ out CachedGraphicsGuestCode guestCode)
+ {
+ var memoryManager = channel.MemoryManager;
+ IdTable idTable = new IdTable();
+ guestCode = new CachedGraphicsGuestCode();
+
+ program = null;
+
+ bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode);
+ found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode);
+ found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode);
+ found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode);
+ found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode);
+ found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode);
+
+ if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
+ {
+ return specList.TryFindForGraphics(channel, ref poolState, ref graphicsState, out program);
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Tries to get the ID of a single cached shader stage.
+ /// </summary>
+ /// <param name="idCache">ID cache of the stage</param>
+ /// <param name="memoryManager">GPU memory manager</param>
+ /// <param name="baseAddress">Base address of the shader</param>
+ /// <param name="id">ID, if found</param>
+ /// <param name="data">Cached guest code, if found</param>
+ /// <returns>True if a cached shader is found, false otherwise</returns>
+ private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data)
+ {
+ if (baseAddress == 0)
+ {
+ id = 0;
+ data = null;
+ return true;
+ }
+
+ ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(memoryManager, baseAddress);
+ return idCache.TryFind(codeAccessor, out id, out data);
+ }
+
+ /// <summary>
+ /// Gets all programs that have been added to the table.
+ /// </summary>
+ /// <returns>Programs added to the table</returns>
+ public IEnumerable<CachedShaderProgram> GetPrograms()
+ {
+ foreach (var specList in _shaderPrograms.Values)
+ {
+ foreach (var program in specList)
+ {
+ yield return program;
+ }
+ }
+ }
+ }
+} \ No newline at end of file