AutoDeleteCache.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Ryujinx.Graphics.Gpu.Image
  5. {
  6. /// <summary>
  7. /// An entry on the short duration texture cache.
  8. /// </summary>
  9. class ShortTextureCacheEntry
  10. {
  11. public bool IsAutoDelete;
  12. public readonly TextureDescriptor Descriptor;
  13. public readonly int InvalidatedSequence;
  14. public readonly Texture Texture;
  15. /// <summary>
  16. /// Create a new entry on the short duration texture cache.
  17. /// </summary>
  18. /// <param name="descriptor">Last descriptor that referenced the texture</param>
  19. /// <param name="texture">The texture</param>
  20. public ShortTextureCacheEntry(TextureDescriptor descriptor, Texture texture)
  21. {
  22. Descriptor = descriptor;
  23. InvalidatedSequence = texture.InvalidatedSequence;
  24. Texture = texture;
  25. }
  26. /// <summary>
  27. /// Create a new entry on the short duration texture cache from the auto delete cache.
  28. /// </summary>
  29. /// <param name="texture">The texture</param>
  30. public ShortTextureCacheEntry(Texture texture)
  31. {
  32. IsAutoDelete = true;
  33. InvalidatedSequence = texture.InvalidatedSequence;
  34. Texture = texture;
  35. }
  36. }
  37. /// <summary>
  38. /// A texture cache that automatically removes older textures that are not used for some time.
  39. /// The cache works with a rotated list with a fixed size. When new textures are added, the
  40. /// old ones at the bottom of the list are deleted.
  41. /// </summary>
  42. class AutoDeleteCache : IEnumerable<Texture>
  43. {
  44. private const int MinCountForDeletion = 32;
  45. private const int MaxCapacity = 2048;
  46. private const ulong MinTextureSizeCapacity = 512 * 1024 * 1024;
  47. private const ulong MaxTextureSizeCapacity = 4UL * 1024 * 1024 * 1024;
  48. private const ulong DefaultTextureSizeCapacity = 1UL * 1024 * 1024 * 1024;
  49. private const float MemoryScaleFactor = 0.50f;
  50. private ulong _maxCacheMemoryUsage = 0;
  51. private readonly LinkedList<Texture> _textures;
  52. private ulong _totalSize;
  53. private HashSet<ShortTextureCacheEntry> _shortCacheBuilder;
  54. private HashSet<ShortTextureCacheEntry> _shortCache;
  55. private readonly Dictionary<TextureDescriptor, ShortTextureCacheEntry> _shortCacheLookup;
  56. /// <summary>
  57. /// Initializes the cache, setting the maximum texture capacity for the specified GPU context.
  58. /// </summary>
  59. /// <remarks>
  60. /// If the backend GPU has 0 memory capacity, the cache size defaults to `DefaultTextureSizeCapacity`.
  61. /// </remarks>
  62. /// <param name="context">The GPU context that the cache belongs to</param>
  63. public void Initialize(GpuContext context)
  64. {
  65. var cacheMemory = (ulong)(context.Capabilities.MaximumGpuMemory * MemoryScaleFactor);
  66. _maxCacheMemoryUsage = Math.Clamp(cacheMemory, MinTextureSizeCapacity, MaxTextureSizeCapacity);
  67. if (context.Capabilities.MaximumGpuMemory == 0)
  68. {
  69. _maxCacheMemoryUsage = DefaultTextureSizeCapacity;
  70. }
  71. }
  72. /// <summary>
  73. /// Creates a new instance of the automatic deletion cache.
  74. /// </summary>
  75. public AutoDeleteCache()
  76. {
  77. _textures = new LinkedList<Texture>();
  78. _shortCacheBuilder = new HashSet<ShortTextureCacheEntry>();
  79. _shortCache = new HashSet<ShortTextureCacheEntry>();
  80. _shortCacheLookup = new Dictionary<TextureDescriptor, ShortTextureCacheEntry>();
  81. }
  82. /// <summary>
  83. /// Adds a new texture to the cache, even if the texture added is already on the cache.
  84. /// </summary>
  85. /// <remarks>
  86. /// Using this method is only recommended if you know that the texture is not yet on the cache,
  87. /// otherwise it would store the same texture more than once.
  88. /// </remarks>
  89. /// <param name="texture">The texture to be added to the cache</param>
  90. public void Add(Texture texture)
  91. {
  92. _totalSize += texture.Size;
  93. texture.IncrementReferenceCount();
  94. texture.CacheNode = _textures.AddLast(texture);
  95. if (_textures.Count > MaxCapacity ||
  96. (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion))
  97. {
  98. RemoveLeastUsedTexture();
  99. }
  100. }
  101. /// <summary>
  102. /// Adds a new texture to the cache, or just moves it to the top of the list if the
  103. /// texture is already on the cache.
  104. /// </summary>
  105. /// <remarks>
  106. /// Moving the texture to the top of the list prevents it from being deleted,
  107. /// as the textures on the bottom of the list are deleted when new ones are added.
  108. /// </remarks>
  109. /// <param name="texture">The texture to be added, or moved to the top</param>
  110. public void Lift(Texture texture)
  111. {
  112. if (texture.CacheNode != null)
  113. {
  114. if (texture.CacheNode != _textures.Last)
  115. {
  116. _textures.Remove(texture.CacheNode);
  117. _textures.AddLast(texture.CacheNode);
  118. }
  119. if (_totalSize > _maxCacheMemoryUsage && _textures.Count >= MinCountForDeletion)
  120. {
  121. RemoveLeastUsedTexture();
  122. }
  123. }
  124. else
  125. {
  126. Add(texture);
  127. }
  128. }
  129. /// <summary>
  130. /// Removes the least used texture from the cache.
  131. /// </summary>
  132. private void RemoveLeastUsedTexture()
  133. {
  134. Texture oldestTexture = _textures.First.Value;
  135. _totalSize -= oldestTexture.Size;
  136. if (!oldestTexture.CheckModified(false))
  137. {
  138. // The texture must be flushed if it falls out of the auto delete cache.
  139. // Flushes out of the auto delete cache do not trigger write tracking,
  140. // as it is expected that other overlapping textures exist that have more up-to-date contents.
  141. oldestTexture.Group.SynchronizeDependents(oldestTexture);
  142. oldestTexture.FlushModified(false);
  143. }
  144. _textures.RemoveFirst();
  145. oldestTexture.DecrementReferenceCount();
  146. oldestTexture.CacheNode = null;
  147. }
  148. /// <summary>
  149. /// Removes a texture from the cache.
  150. /// </summary>
  151. /// <param name="texture">The texture to be removed from the cache</param>
  152. /// <param name="flush">True to remove the texture if it was on the cache</param>
  153. /// <returns>True if the texture was found and removed, false otherwise</returns>
  154. public bool Remove(Texture texture, bool flush)
  155. {
  156. if (texture.CacheNode == null)
  157. {
  158. return false;
  159. }
  160. // Remove our reference to this texture.
  161. if (flush)
  162. {
  163. texture.FlushModified(false);
  164. }
  165. _textures.Remove(texture.CacheNode);
  166. _totalSize -= texture.Size;
  167. texture.CacheNode = null;
  168. return texture.DecrementReferenceCount();
  169. }
  170. /// <summary>
  171. /// Attempt to find a texture on the short duration cache.
  172. /// </summary>
  173. /// <param name="descriptor">The texture descriptor</param>
  174. /// <returns>The texture if found, null otherwise</returns>
  175. public Texture FindShortCache(in TextureDescriptor descriptor)
  176. {
  177. if (_shortCacheLookup.Count > 0 && _shortCacheLookup.TryGetValue(descriptor, out var entry))
  178. {
  179. if (entry.InvalidatedSequence == entry.Texture.InvalidatedSequence)
  180. {
  181. return entry.Texture;
  182. }
  183. else
  184. {
  185. _shortCacheLookup.Remove(descriptor);
  186. }
  187. }
  188. return null;
  189. }
  190. /// <summary>
  191. /// Removes a texture from the short duration cache.
  192. /// </summary>
  193. /// <param name="texture">Texture to remove from the short cache</param>
  194. public void RemoveShortCache(Texture texture)
  195. {
  196. bool removed = _shortCache.Remove(texture.ShortCacheEntry);
  197. removed |= _shortCacheBuilder.Remove(texture.ShortCacheEntry);
  198. if (removed)
  199. {
  200. texture.DecrementReferenceCount();
  201. if (!texture.ShortCacheEntry.IsAutoDelete)
  202. {
  203. _shortCacheLookup.Remove(texture.ShortCacheEntry.Descriptor);
  204. }
  205. texture.ShortCacheEntry = null;
  206. }
  207. }
  208. /// <summary>
  209. /// Adds a texture to the short duration cache.
  210. /// It starts in the builder set, and it is moved into the deletion set on next process.
  211. /// </summary>
  212. /// <param name="texture">Texture to add to the short cache</param>
  213. /// <param name="descriptor">Last used texture descriptor</param>
  214. public void AddShortCache(Texture texture, ref TextureDescriptor descriptor)
  215. {
  216. var entry = new ShortTextureCacheEntry(descriptor, texture);
  217. _shortCacheBuilder.Add(entry);
  218. _shortCacheLookup.Add(entry.Descriptor, entry);
  219. texture.ShortCacheEntry = entry;
  220. texture.IncrementReferenceCount();
  221. }
  222. /// <summary>
  223. /// Adds a texture to the short duration cache without a descriptor. This typically keeps it alive for two ticks.
  224. /// On expiry, it will be removed from the AutoDeleteCache.
  225. /// </summary>
  226. /// <param name="texture">Texture to add to the short cache</param>
  227. public void AddShortCache(Texture texture)
  228. {
  229. if (texture.ShortCacheEntry != null)
  230. {
  231. var entry = new ShortTextureCacheEntry(texture);
  232. _shortCacheBuilder.Add(entry);
  233. texture.ShortCacheEntry = entry;
  234. texture.IncrementReferenceCount();
  235. }
  236. }
  237. /// <summary>
  238. /// Delete textures from the short duration cache.
  239. /// Moves the builder set to be deleted on next process.
  240. /// </summary>
  241. public void ProcessShortCache()
  242. {
  243. HashSet<ShortTextureCacheEntry> toRemove = _shortCache;
  244. foreach (var entry in toRemove)
  245. {
  246. entry.Texture.DecrementReferenceCount();
  247. if (entry.IsAutoDelete)
  248. {
  249. Remove(entry.Texture, false);
  250. }
  251. else
  252. {
  253. _shortCacheLookup.Remove(entry.Descriptor);
  254. }
  255. entry.Texture.ShortCacheEntry = null;
  256. }
  257. toRemove.Clear();
  258. _shortCache = _shortCacheBuilder;
  259. _shortCacheBuilder = toRemove;
  260. }
  261. public IEnumerator<Texture> GetEnumerator()
  262. {
  263. return _textures.GetEnumerator();
  264. }
  265. IEnumerator IEnumerable.GetEnumerator()
  266. {
  267. return _textures.GetEnumerator();
  268. }
  269. }
  270. }