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