AutoDeleteCache.cs 12 KB

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