SurfaceCache.cs 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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 CodecId CodecId;
  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, CodecId codecId, uint lumaOffset, uint chromaOffset, int width, int height)
  32. {
  33. ISurface surface = null;
  34. // Try to find a compatible surface with same parameters, and same offsets.
  35. for (int i = 0; i < MaxItems; i++)
  36. {
  37. ref CacheItem item = ref _pool[i];
  38. if (item.LumaOffset == lumaOffset &&
  39. item.ChromaOffset == chromaOffset &&
  40. item.CodecId == codecId &&
  41. item.Width == width &&
  42. item.Height == height)
  43. {
  44. item.ReferenceCount++;
  45. surface = item.Surface;
  46. MoveToFront(i);
  47. break;
  48. }
  49. }
  50. // If we failed to find a perfect match, now ignore the offsets.
  51. // Search backwards to replace the oldest compatible surface,
  52. // this avoids thrashing frquently used surfaces.
  53. // Now we need to ensure that the surface is not in use, as we'll change the data.
  54. if (surface == null)
  55. {
  56. for (int i = MaxItems - 1; i >= 0; i--)
  57. {
  58. ref CacheItem item = ref _pool[i];
  59. if (item.ReferenceCount == 0 && item.CodecId == codecId && item.Width == width && item.Height == height)
  60. {
  61. item.ReferenceCount = 1;
  62. item.LumaOffset = lumaOffset;
  63. item.ChromaOffset = chromaOffset;
  64. surface = item.Surface;
  65. if ((lumaOffset | chromaOffset) != 0)
  66. {
  67. SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset);
  68. }
  69. MoveToFront(i);
  70. break;
  71. }
  72. }
  73. }
  74. // If everything else failed, we try to create a new surface,
  75. // and insert it on the pool. We replace the oldest item on the
  76. // pool to avoid thrashing frequently used surfaces.
  77. // If even the oldest item is in use, that means that the entire pool
  78. // is in use, in that case we throw as there's no place to insert
  79. // the new surface.
  80. if (surface == null)
  81. {
  82. if (_pool[MaxItems - 1].ReferenceCount == 0)
  83. {
  84. surface = decoder.CreateSurface(width, height);
  85. if ((lumaOffset | chromaOffset) != 0)
  86. {
  87. SurfaceReader.Read(_gmm, surface, lumaOffset, chromaOffset);
  88. }
  89. MoveToFront(MaxItems - 1);
  90. ref CacheItem item = ref _pool[0];
  91. item.Surface?.Dispose();
  92. item.ReferenceCount = 1;
  93. item.LumaOffset = lumaOffset;
  94. item.ChromaOffset = chromaOffset;
  95. item.Width = width;
  96. item.Height = height;
  97. item.CodecId = codecId;
  98. item.Surface = surface;
  99. }
  100. else
  101. {
  102. throw new InvalidOperationException("No free slot on the surface pool.");
  103. }
  104. }
  105. return surface;
  106. }
  107. public void Put(ISurface surface)
  108. {
  109. for (int i = 0; i < MaxItems; i++)
  110. {
  111. ref CacheItem item = ref _pool[i];
  112. if (item.Surface == surface)
  113. {
  114. item.ReferenceCount--;
  115. Debug.Assert(item.ReferenceCount >= 0);
  116. break;
  117. }
  118. }
  119. }
  120. private void MoveToFront(int index)
  121. {
  122. // If index is 0 we don't need to do anything,
  123. // as it's already on the front.
  124. if (index != 0)
  125. {
  126. CacheItem temp = _pool[index];
  127. Array.Copy(_pool, 0, _pool, 1, index);
  128. _pool[0] = temp;
  129. }
  130. }
  131. }
  132. }