ShaderCacheHashTable.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using Ryujinx.Graphics.Gpu.Memory;
  2. using Ryujinx.Graphics.Gpu.Shader.HashTable;
  3. using Ryujinx.Graphics.Shader;
  4. using System;
  5. using System.Collections.Generic;
  6. namespace Ryujinx.Graphics.Gpu.Shader
  7. {
  8. /// <summary>
  9. /// Holds already cached code for a guest shader.
  10. /// </summary>
  11. struct CachedGraphicsGuestCode
  12. {
  13. public byte[] VertexACode;
  14. public byte[] VertexBCode;
  15. public byte[] TessControlCode;
  16. public byte[] TessEvaluationCode;
  17. public byte[] GeometryCode;
  18. public byte[] FragmentCode;
  19. /// <summary>
  20. /// Gets the guest code of a shader stage by its index.
  21. /// </summary>
  22. /// <param name="stageIndex">Index of the shader stage</param>
  23. /// <returns>Guest code, or null if not present</returns>
  24. public byte[] GetByIndex(int stageIndex)
  25. {
  26. return stageIndex switch
  27. {
  28. 1 => TessControlCode,
  29. 2 => TessEvaluationCode,
  30. 3 => GeometryCode,
  31. 4 => FragmentCode,
  32. _ => VertexBCode
  33. };
  34. }
  35. }
  36. /// <summary>
  37. /// Graphics shader cache hash table.
  38. /// </summary>
  39. class ShaderCacheHashTable
  40. {
  41. /// <summary>
  42. /// Shader ID cache.
  43. /// </summary>
  44. private struct IdCache
  45. {
  46. private PartitionedHashTable<int> _cache;
  47. private int _id;
  48. /// <summary>
  49. /// Initializes the state.
  50. /// </summary>
  51. public void Initialize()
  52. {
  53. _cache = new PartitionedHashTable<int>();
  54. _id = 0;
  55. }
  56. /// <summary>
  57. /// Adds guest code to the cache.
  58. /// </summary>
  59. /// <remarks>
  60. /// If the code was already cached, it will just return the existing ID.
  61. /// </remarks>
  62. /// <param name="code">Code to add</param>
  63. /// <returns>Unique ID for the guest code</returns>
  64. public int Add(byte[] code)
  65. {
  66. int id = ++_id;
  67. int cachedId = _cache.GetOrAdd(code, id);
  68. if (cachedId != id)
  69. {
  70. --_id;
  71. }
  72. return cachedId;
  73. }
  74. /// <summary>
  75. /// Tries to find cached guest code.
  76. /// </summary>
  77. /// <param name="dataAccessor">Code accessor used to read guest code to find a match on the hash table</param>
  78. /// <param name="id">ID of the guest code, if found</param>
  79. /// <param name="data">Cached guest code, if found</param>
  80. /// <returns>True if found, false otherwise</returns>
  81. public bool TryFind(IDataAccessor dataAccessor, out int id, out byte[] data)
  82. {
  83. return _cache.TryFindItem(dataAccessor, out id, out data);
  84. }
  85. }
  86. /// <summary>
  87. /// Guest code IDs of the guest shaders that when combined forms a single host program.
  88. /// </summary>
  89. private struct IdTable : IEquatable<IdTable>
  90. {
  91. public int VertexAId;
  92. public int VertexBId;
  93. public int TessControlId;
  94. public int TessEvaluationId;
  95. public int GeometryId;
  96. public int FragmentId;
  97. public override bool Equals(object obj)
  98. {
  99. return obj is IdTable other && Equals(other);
  100. }
  101. public bool Equals(IdTable other)
  102. {
  103. return other.VertexAId == VertexAId &&
  104. other.VertexBId == VertexBId &&
  105. other.TessControlId == TessControlId &&
  106. other.TessEvaluationId == TessEvaluationId &&
  107. other.GeometryId == GeometryId &&
  108. other.FragmentId == FragmentId;
  109. }
  110. public override int GetHashCode()
  111. {
  112. return HashCode.Combine(VertexAId, VertexBId, TessControlId, TessEvaluationId, GeometryId, FragmentId);
  113. }
  114. }
  115. private IdCache _vertexACache;
  116. private IdCache _vertexBCache;
  117. private IdCache _tessControlCache;
  118. private IdCache _tessEvaluationCache;
  119. private IdCache _geometryCache;
  120. private IdCache _fragmentCache;
  121. private readonly Dictionary<IdTable, ShaderSpecializationList> _shaderPrograms;
  122. /// <summary>
  123. /// Creates a new graphics shader cache hash table.
  124. /// </summary>
  125. public ShaderCacheHashTable()
  126. {
  127. _vertexACache.Initialize();
  128. _vertexBCache.Initialize();
  129. _tessControlCache.Initialize();
  130. _tessEvaluationCache.Initialize();
  131. _geometryCache.Initialize();
  132. _fragmentCache.Initialize();
  133. _shaderPrograms = new Dictionary<IdTable, ShaderSpecializationList>();
  134. }
  135. /// <summary>
  136. /// Adds a program to the cache.
  137. /// </summary>
  138. /// <param name="program">Program to be added</param>
  139. public void Add(CachedShaderProgram program)
  140. {
  141. IdTable idTable = new IdTable();
  142. foreach (var shader in program.Shaders)
  143. {
  144. if (shader == null)
  145. {
  146. continue;
  147. }
  148. if (shader.Info != null)
  149. {
  150. switch (shader.Info.Stage)
  151. {
  152. case ShaderStage.Vertex:
  153. idTable.VertexBId = _vertexBCache.Add(shader.Code);
  154. break;
  155. case ShaderStage.TessellationControl:
  156. idTable.TessControlId = _tessControlCache.Add(shader.Code);
  157. break;
  158. case ShaderStage.TessellationEvaluation:
  159. idTable.TessEvaluationId = _tessEvaluationCache.Add(shader.Code);
  160. break;
  161. case ShaderStage.Geometry:
  162. idTable.GeometryId = _geometryCache.Add(shader.Code);
  163. break;
  164. case ShaderStage.Fragment:
  165. idTable.FragmentId = _fragmentCache.Add(shader.Code);
  166. break;
  167. }
  168. }
  169. else
  170. {
  171. idTable.VertexAId = _vertexACache.Add(shader.Code);
  172. }
  173. }
  174. if (!_shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
  175. {
  176. specList = new ShaderSpecializationList();
  177. _shaderPrograms.Add(idTable, specList);
  178. }
  179. specList.Add(program);
  180. }
  181. /// <summary>
  182. /// Tries to find a cached program.
  183. /// </summary>
  184. /// <remarks>
  185. /// Even if false is returned, <paramref name="guestCode"/> might still contain cached guest code.
  186. /// This can be used to avoid additional allocations for guest code that was already cached.
  187. /// </remarks>
  188. /// <param name="channel">GPU channel</param>
  189. /// <param name="poolState">Texture pool state</param>
  190. /// <param name="graphicsState">Graphics state</param>
  191. /// <param name="addresses">Guest addresses of the shaders to find</param>
  192. /// <param name="program">Cached host program for the given state, if found</param>
  193. /// <param name="guestCode">Cached guest code, if any found</param>
  194. /// <returns>True if a cached host program was found, false otherwise</returns>
  195. public bool TryFind(
  196. GpuChannel channel,
  197. ref GpuChannelPoolState poolState,
  198. ref GpuChannelGraphicsState graphicsState,
  199. ShaderAddresses addresses,
  200. out CachedShaderProgram program,
  201. out CachedGraphicsGuestCode guestCode)
  202. {
  203. var memoryManager = channel.MemoryManager;
  204. IdTable idTable = new IdTable();
  205. guestCode = new CachedGraphicsGuestCode();
  206. program = null;
  207. bool found = TryGetId(_vertexACache, memoryManager, addresses.VertexA, out idTable.VertexAId, out guestCode.VertexACode);
  208. found &= TryGetId(_vertexBCache, memoryManager, addresses.VertexB, out idTable.VertexBId, out guestCode.VertexBCode);
  209. found &= TryGetId(_tessControlCache, memoryManager, addresses.TessControl, out idTable.TessControlId, out guestCode.TessControlCode);
  210. found &= TryGetId(_tessEvaluationCache, memoryManager, addresses.TessEvaluation, out idTable.TessEvaluationId, out guestCode.TessEvaluationCode);
  211. found &= TryGetId(_geometryCache, memoryManager, addresses.Geometry, out idTable.GeometryId, out guestCode.GeometryCode);
  212. found &= TryGetId(_fragmentCache, memoryManager, addresses.Fragment, out idTable.FragmentId, out guestCode.FragmentCode);
  213. if (found && _shaderPrograms.TryGetValue(idTable, out ShaderSpecializationList specList))
  214. {
  215. return specList.TryFindForGraphics(channel, ref poolState, ref graphicsState, out program);
  216. }
  217. return false;
  218. }
  219. /// <summary>
  220. /// Tries to get the ID of a single cached shader stage.
  221. /// </summary>
  222. /// <param name="idCache">ID cache of the stage</param>
  223. /// <param name="memoryManager">GPU memory manager</param>
  224. /// <param name="baseAddress">Base address of the shader</param>
  225. /// <param name="id">ID, if found</param>
  226. /// <param name="data">Cached guest code, if found</param>
  227. /// <returns>True if a cached shader is found, false otherwise</returns>
  228. private static bool TryGetId(IdCache idCache, MemoryManager memoryManager, ulong baseAddress, out int id, out byte[] data)
  229. {
  230. if (baseAddress == 0)
  231. {
  232. id = 0;
  233. data = null;
  234. return true;
  235. }
  236. ShaderCodeAccessor codeAccessor = new ShaderCodeAccessor(memoryManager, baseAddress);
  237. return idCache.TryFind(codeAccessor, out id, out data);
  238. }
  239. /// <summary>
  240. /// Gets all programs that have been added to the table.
  241. /// </summary>
  242. /// <returns>Programs added to the table</returns>
  243. public IEnumerable<CachedShaderProgram> GetPrograms()
  244. {
  245. foreach (var specList in _shaderPrograms.Values)
  246. {
  247. foreach (var program in specList)
  248. {
  249. yield return program;
  250. }
  251. }
  252. }
  253. }
  254. }