/ src / Ryujinx.Graphics.Nvdec / Image / SurfaceCache.cs
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  }