SurfaceCache.cs 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. using Ryujinx.Graphics.Gpu.Memory;
  2. using Ryujinx.Graphics.Video;
  3. using System;
  4. using System.Diagnostics;
  5. namespace Ryujinx.Graphics.Nvdec.Image
  6. {
  7. class SurfaceCache
  8. {
  9. // Must be equal to at least the maximum number of surfaces
  10. // that can be in use simultaneously (which is 17, since H264
  11. // can have up to 16 reference frames, and we need another one
  12. // for the current frame).
  13. // Realistically, most codecs won't ever use more than 4 simultaneously.
  14. private const int MaxItems = 17;
  15. private struct CacheItem
  16. {
  17. public int ReferenceCount;
  18. public uint LumaOffset;
  19. public uint ChromaOffset;
  20. public int Width;
  21. public int Height;
  22. public IDecoder Owner;
  23. public ISurface Surface;
  24. }
  25. private readonly CacheItem[] _pool = new CacheItem[MaxItems];
  26. private readonly MemoryManager _gmm;
  27. public SurfaceCache(MemoryManager gmm)
  28. {
  29. _gmm = gmm;
  30. }
  31. public ISurface Get(IDecoder decoder, uint lumaOffset, uint chromaOffset, int width, int height)
  32. {
  33. lock (_pool)
  34. {
  35. ISurface surface = null;
  36. // Try to find a compatible surface with same parameters, and same offsets.
  37. for (int i = 0; i < MaxItems; i++)
  38. {
  39. ref CacheItem item = ref _pool[i];
  40. if (item.LumaOffset == lumaOffset &&
  41. item.ChromaOffset == chromaOffset &&
  42. item.Owner == decoder &&
  43. item.Width == width &&
  44. item.Height == height)
  45. {
  46. item.ReferenceCount++;
  47. surface = item.Surface;
  48. MoveToFront(i);
  49. break;
  50. }
  51. }
  52. // If we failed to find a perfect match, now ignore the offsets.
  53. // Search backwards to replace the oldest compatible surface,
  54. // this avoids thrashing frequently used surfaces.
  55. // Now we need to ensure that the surface is not in use, as we'll change the data.
  56. if (surface == null)
  57. {
  58. for (int i = MaxItems - 1; i >= 0; i--)
  59. {
  60. ref CacheItem item = ref _pool[i];
  61. if (item.ReferenceCount == 0 && item.Owner == decoder && item.Width == width && item.Height == height)
  62. {
  63. item.ReferenceCount = 1;
  64. item.LumaOffset = lumaOffset;
  65. item.ChromaOffset = chromaOffset;
  66. surface = item.Surface;
  67. if ((lumaOffset | chromaOffset) != 0)
  68. {
  69. SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset);
  70. }
  71. MoveToFront(i);
  72. break;
  73. }
  74. }
  75. }
  76. // If everything else failed, we try to create a new surface,
  77. // and insert it on the pool. We replace the oldest item on the
  78. // pool to avoid thrashing frequently used surfaces.
  79. // If even the oldest item is in use, that means that the entire pool
  80. // is in use, in that case we throw as there's no place to insert
  81. // the new surface.
  82. if (surface == null)
  83. {
  84. if (_pool[MaxItems - 1].ReferenceCount == 0)
  85. {
  86. surface = decoder.CreateSurface(width, height);
  87. if ((lumaOffset | chromaOffset) != 0)
  88. {
  89. SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset);
  90. }
  91. MoveToFront(MaxItems - 1);
  92. ref CacheItem item = ref _pool[0];
  93. item.Surface?.Dispose();
  94. item.ReferenceCount = 1;
  95. item.LumaOffset = lumaOffset;
  96. item.ChromaOffset = chromaOffset;
  97. item.Width = width;
  98. item.Height = height;
  99. item.Owner = decoder;
  100. item.Surface = surface;
  101. }
  102. else
  103. {
  104. throw new InvalidOperationException("No free slot on the surface pool.");
  105. }
  106. }
  107. return surface;
  108. }
  109. }
  110. public void Put(ISurface surface)
  111. {
  112. lock (_pool)
  113. {
  114. for (int i = 0; i < MaxItems; i++)
  115. {
  116. ref CacheItem item = ref _pool[i];
  117. if (item.Surface == surface)
  118. {
  119. item.ReferenceCount--;
  120. Debug.Assert(item.ReferenceCount >= 0);
  121. break;
  122. }
  123. }
  124. }
  125. }
  126. private void MoveToFront(int index)
  127. {
  128. // If index is 0 we don't need to do anything,
  129. // as it's already on the front.
  130. if (index != 0)
  131. {
  132. CacheItem temp = _pool[index];
  133. Array.Copy(_pool, 0, _pool, 1, index);
  134. _pool[0] = temp;
  135. }
  136. }
  137. public void Trim()
  138. {
  139. lock (_pool)
  140. {
  141. for (int i = 0; i < MaxItems; i++)
  142. {
  143. ref CacheItem item = ref _pool[i];
  144. if (item.ReferenceCount == 0)
  145. {
  146. item.Surface?.Dispose();
  147. item = default;
  148. }
  149. }
  150. }
  151. }
  152. }
  153. }