Implement a conservative cache

This commit is contained in:
ReinUsesLisp 2018-09-21 16:32:32 -03:00
parent 7de7b559ad
commit f88a7a8596
12 changed files with 428 additions and 248 deletions

View file

@ -2,7 +2,7 @@ using Ryujinx.Graphics.Texture;
namespace Ryujinx.Graphics.Gal
{
public struct GalImage
public struct GalImage : ICompatible<GalImage>
{
public int Width;
public int Height;
@ -59,5 +59,10 @@ namespace Ryujinx.Graphics.Gal
return Height == Image.Height;
}
public bool IsCompatible(GalImage Other)
{
return Format == Other.Format && SizeMatches(Other);
}
}
}

View file

@ -5,9 +5,9 @@ namespace Ryujinx.Graphics.Gal
void LockCache();
void UnlockCache();
void Create(long Key, int Size, GalImage Image);
void CreateEmpty(long Key, int Size, GalImage Image);
void Create(long Key, byte[] Data, GalImage Image);
void CreateData(long Key, byte[] Data, GalImage Image);
bool TryGetImage(long Key, out GalImage Image);

View file

@ -0,0 +1,24 @@
using OpenTK.Graphics.OpenGL;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class BufferParams : ICompatible<BufferParams>
{
public BufferTarget Target { get; private set; }
public long Size { get; private set; }
public BufferParams(BufferTarget Target, long Size)
{
this.Target = Target;
this.Size = Size;
}
public bool IsCompatible(BufferParams Other)
{
//Target is not needed for compatibility
return Size == Other.Size;
}
}
}

View file

@ -2,7 +2,7 @@
namespace Ryujinx.Graphics.Gal.OpenGL
{
class ImageHandler
class ImageHandler : Resource
{
public GalImage Image { get; private set; }

View file

@ -3,57 +3,46 @@ using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLCachedResource<T>
class OGLCachedResource<TKey, TValue>
where TKey : ICompatible<TKey>
where TValue : Resource
{
public delegate void DeleteValue(T Value);
private const int MaxTimeDelta = 5 * 60000;
private const int MaxRemovalsPerRun = 10;
private struct CacheBucket
{
public T Value { get; private set; }
public LinkedListNode<long> Node { get; private set; }
public TValue Value { get; private set; }
public long DataSize { get; private set; }
public int Timestamp { get; private set; }
public CacheBucket(T Value, long DataSize, LinkedListNode<long> Node)
public CacheBucket(TValue Value, long DataSize)
{
this.Value = Value;
this.DataSize = DataSize;
this.Node = Node;
Timestamp = Environment.TickCount;
}
}
private Dictionary<long, CacheBucket> Cache;
private LinkedList<long> SortedCache;
private DeleteValue DeleteValueCallback;
private Queue<T> DeletePending;
private ResourcePool<TKey, TValue> Pool;
private bool Locked;
public OGLCachedResource(DeleteValue DeleteValueCallback)
public OGLCachedResource(
ResourcePool<TKey, TValue>.CreateValue CreateValueCallback,
ResourcePool<TKey, TValue>.DeleteValue DeleteValueCallback)
{
if (CreateValueCallback == null)
{
throw new ArgumentNullException(nameof(CreateValueCallback));
}
if (DeleteValueCallback == null)
{
throw new ArgumentNullException(nameof(DeleteValueCallback));
}
this.DeleteValueCallback = DeleteValueCallback;
Cache = new Dictionary<long, CacheBucket>();
SortedCache = new LinkedList<long>();
DeletePending = new Queue<T>();
Pool = new ResourcePool<TKey, TValue>(CreateValueCallback, DeleteValueCallback);
}
public void Lock()
@ -65,62 +54,40 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
Locked = false;
while (DeletePending.TryDequeue(out T Value))
{
DeleteValueCallback(Value);
}
ClearCacheIfNeeded();
Pool.ReleaseMemory();
}
public void AddOrUpdate(long Key, T Value, long Size)
public TValue CreateOrRecycle(long Key, TKey Parameters, long Size)
{
if (!Locked)
{
ClearCacheIfNeeded();
Pool.ReleaseMemory();
}
LinkedListNode<long> Node = SortedCache.AddLast(Key);
CacheBucket NewBucket = new CacheBucket(Value, Size, Node);
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
if (Locked)
{
DeletePending.Enqueue(Bucket.Value);
}
else
{
DeleteValueCallback(Bucket.Value);
}
SortedCache.Remove(Bucket.Node);
Cache[Key] = NewBucket;
}
else
{
Cache.Add(Key, NewBucket);
Bucket.Value.MarkAsUnused();
}
TValue Value = Pool.CreateOrRecycle(Parameters);
Cache[Key] = new CacheBucket(Value, Size);
return Value;
}
public bool TryGetValue(long Key, out T Value)
public bool TryGetValue(long Key, out TValue Value)
{
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
Value = Bucket.Value;
SortedCache.Remove(Bucket.Node);
LinkedListNode<long> Node = SortedCache.AddLast(Key);
Cache[Key] = new CacheBucket(Value, Bucket.DataSize, Node);
Value.UpdateStamp();
return true;
}
Value = default(T);
Value = default(TValue);
return false;
}
@ -138,49 +105,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL
return false;
}
private void ClearCacheIfNeeded()
{
int Timestamp = Environment.TickCount;
int Count = 0;
while (Count++ < MaxRemovalsPerRun)
{
LinkedListNode<long> Node = SortedCache.First;
if (Node == null)
{
break;
}
CacheBucket Bucket = Cache[Node.Value];
int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp);
if ((uint)TimeDelta <= (uint)MaxTimeDelta)
{
break;
}
SortedCache.Remove(Node);
Cache.Remove(Node.Value);
DeleteValueCallback(Bucket.Value);
}
}
private int RingDelta(int Old, int New)
{
if ((uint)New < (uint)Old)
{
return New + (~Old + 1);
}
else
{
return New - Old;
}
}
}
}

View file

@ -5,11 +5,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLConstBuffer : IGalConstBuffer
{
private OGLCachedResource<OGLStreamBuffer> Cache;
private OGLCachedResource<BufferParams, OGLStreamBuffer> Cache;
public OGLConstBuffer()
{
Cache = new OGLCachedResource<OGLStreamBuffer>(DeleteBuffer);
Cache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
}
public void LockCache()
@ -24,9 +24,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void Create(long Key, long Size)
{
OGLStreamBuffer Buffer = new OGLStreamBuffer(BufferTarget.UniformBuffer, Size);
BufferParams Params = new BufferParams(BufferTarget.UniformBuffer, Size);
Cache.AddOrUpdate(Key, Buffer, Size);
Cache.CreateOrRecycle(Key, Params, Size);
}
public bool IsCached(long Key, long Size)
@ -56,6 +56,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
return false;
}
private static OGLStreamBuffer CreateBuffer(BufferParams Params)
{
return new OGLStreamBuffer(Params.Target, Params.Size);
}
private static void DeleteBuffer(OGLStreamBuffer Buffer)
{
Buffer.Dispose();

View file

@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
private int[] VertexBuffers;
private OGLCachedResource<int> VboCache;
private OGLCachedResource<int> IboCache;
private OGLCachedResource<BufferParams, OGLStreamBuffer> VboCache;
private OGLCachedResource<BufferParams, OGLStreamBuffer> IboCache;
private struct IbInfo
{
@ -26,8 +26,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
VertexBuffers = new int[32];
VboCache = new OGLCachedResource<int>(GL.DeleteBuffer);
IboCache = new OGLCachedResource<int>(GL.DeleteBuffer);
VboCache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
IboCache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
IndexBuffer = new IbInfo();
@ -97,26 +97,28 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void CreateVbo(long Key, int DataSize, IntPtr HostAddress)
{
int Handle = GL.GenBuffer();
BufferParams Params = new BufferParams(BufferTarget.ArrayBuffer, DataSize);
VboCache.AddOrUpdate(Key, Handle, (uint)DataSize);
OGLStreamBuffer CachedBuffer = VboCache.CreateOrRecycle(Key, Params, (uint)DataSize);
IntPtr Length = new IntPtr(DataSize);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
GL.BindBuffer(BufferTarget.ArrayBuffer, CachedBuffer.Handle);
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, Length, HostAddress);
}
public void CreateIbo(long Key, int DataSize, IntPtr HostAddress)
{
int Handle = GL.GenBuffer();
BufferParams Params = new BufferParams(BufferTarget.ElementArrayBuffer, DataSize);
IboCache.AddOrUpdate(Key, Handle, (uint)DataSize);
OGLStreamBuffer CachedBuffer = IboCache.CreateOrRecycle(Key, Params, (uint)DataSize);
IntPtr Length = new IntPtr(DataSize);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, CachedBuffer.Handle);
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, Length, HostAddress);
}
public void SetIndexArray(int Size, GalIndexFormat Format)
@ -140,14 +142,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void DrawElements(long IboKey, int First, int VertexBase, GalPrimitiveType PrimType)
{
if (!IboCache.TryGetValue(IboKey, out int IboHandle))
if (!IboCache.TryGetValue(IboKey, out OGLStreamBuffer CachedBuffer))
{
return;
}
PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, IboHandle);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, CachedBuffer.Handle);
First <<= IndexBuffer.ElemSizeLog2;
@ -165,7 +167,26 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public bool TryGetVbo(long VboKey, out int VboHandle)
{
return VboCache.TryGetValue(VboKey, out VboHandle);
if (VboCache.TryGetValue(VboKey, out OGLStreamBuffer CachedBuffer))
{
VboHandle = CachedBuffer.Handle;
return true;
}
VboHandle = 0;
return false;
}
private static OGLStreamBuffer CreateBuffer(BufferParams Params)
{
return new OGLStreamBuffer(Params.Target, (int)Params.Size);
}
private static void DeleteBuffer(OGLStreamBuffer CachedBuffer)
{
CachedBuffer.Dispose();
}
}
}

View file

@ -382,11 +382,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyPBO);
Texture.Create(Key, ImageUtils.GetSize(NewImage), NewImage);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
Texture.CreatePBO(Key, ImageUtils.GetSize(NewImage), NewImage, CopyPBO);
}
private static FramebufferAttachment GetAttachment(ImageHandler CachedImage)

View file

@ -3,7 +3,7 @@ using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLStreamBuffer : IDisposable
class OGLStreamBuffer : Resource, IDisposable
{
public int Handle { get; protected set; }

View file

@ -6,11 +6,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLTexture : IGalTexture
{
private OGLCachedResource<ImageHandler> TextureCache;
private OGLCachedResource<GalImage, ImageHandler> TextureCache;
public OGLTexture()
{
TextureCache = new OGLCachedResource<ImageHandler>(DeleteTexture);
TextureCache = new OGLCachedResource<GalImage, ImageHandler>(CreateTexture, DeleteTexture);
}
public void LockCache()
@ -23,130 +23,125 @@ namespace Ryujinx.Graphics.Gal.OpenGL
TextureCache.Unlock();
}
private static ImageHandler CreateTexture(GalImage Image)
{
const int Level = 0; //TODO: Support mipmap textures.
const int Border = 0;
GalImage Native = ConvertToNativeImage(Image);
int Handle = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, Handle);
//Compressed images are stored when they are set
if (!ImageUtils.IsCompressed(Native.Format))
{
(PixelInternalFormat InternalFmt,
PixelFormat Format,
PixelType Type) = OGLEnumConverter.GetImageFormat(Native.Format);
//TODO: Use ARB_texture_storage when available
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Native.Width,
Native.Height,
Border,
Format,
Type,
IntPtr.Zero);
}
return new ImageHandler(Handle, Image);
}
private static void DeleteTexture(ImageHandler CachedImage)
{
GL.DeleteTexture(CachedImage.Handle);
}
public void Create(long Key, int Size, GalImage Image)
public void CreateEmpty(long Key, int Size, GalImage Image)
{
int Handle = GL.GenTexture();
GL.BindTexture(TextureTarget.Texture2D, Handle);
const int Level = 0; //TODO: Support mipmap textures.
const int Border = 0;
TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Size);
GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask;
bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END;
if (ImageUtils.IsCompressed(Image.Format) && !IsASTC)
{
InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format);
GL.CompressedTexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Image.Width,
Image.Height,
Border,
Size,
IntPtr.Zero);
}
else
{
(PixelInternalFormat InternalFmt,
PixelFormat Format,
PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format);
GL.TexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Image.Width,
Image.Height,
Border,
Format,
Type,
IntPtr.Zero);
}
TextureCache.CreateOrRecycle(Key, Image, Size);
}
public void Create(long Key, byte[] Data, GalImage Image)
public void CreatePBO(long Key, int Size, GalImage Image, int PBO)
{
int Handle = GL.GenTexture();
const int Level = 0; //TODO: Support mipmap textures.
GL.BindTexture(TextureTarget.Texture2D, Handle);
GalImage Native = ConvertToNativeImage(Image);
if (ImageUtils.IsCompressed(Native.Format))
{
throw new NotImplementedException("Compressed PBO creation is not implemented");
}
ImageHandler CachedImage = TextureCache.CreateOrRecycle(Key, Image, Size);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, PBO);
(_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Native.Format);
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
GL.TexSubImage2D(
TextureTarget.Texture2D,
Level,
0,
0,
Native.Width,
Native.Height,
Format,
Type,
IntPtr.Zero);
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
}
public void CreateData(long Key, byte[] Data, GalImage Image)
{
const int Level = 0; //TODO: Support mipmap textures.
const int Border = 0;
TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length);
GalImage Native = ConvertToNativeImage(Image);
GalImageFormat TypeLess = Image.Format & GalImageFormat.FormatMask;
byte[] NativeData = ConvertToNativeData(Image, Data);
bool IsASTC = TypeLess >= GalImageFormat.ASTC_BEGIN && TypeLess <= GalImageFormat.ASTC_END;
ImageHandler CachedImage = TextureCache.CreateOrRecycle(Key, Image, (uint)Data.Length);
if (ImageUtils.IsCompressed(Image.Format) && !IsASTC)
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
if (ImageUtils.IsCompressed(Native.Format))
{
InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format);
InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Native.Format);
GL.CompressedTexImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Image.Width,
Image.Height,
Native.Width,
Native.Height,
Border,
Data.Length,
Data);
NativeData);
}
else
{
//TODO: Use KHR_texture_compression_astc_hdr when available
if (IsASTC)
{
int TextureBlockWidth = ImageUtils.GetBlockWidth(Image.Format);
int TextureBlockHeight = ImageUtils.GetBlockHeight(Image.Format);
(_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Native.Format);
Data = ASTCDecoder.DecodeToRGBA8888(
Data,
TextureBlockWidth,
TextureBlockHeight, 1,
Image.Width,
Image.Height, 1);
Image.Format = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm;
}
else if (TypeLess == GalImageFormat.G8R8)
{
Data = ImageConverter.G8R8ToR8G8(
Data,
Image.Width,
Image.Height,
1);
Image.Format = GalImageFormat.R8G8 | (Image.Format & GalImageFormat.TypeMask);
}
(PixelInternalFormat InternalFmt,
PixelFormat Format,
PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format);
GL.TexImage2D(
GL.TexSubImage2D(
TextureTarget.Texture2D,
Level,
InternalFmt,
Image.Width,
Image.Height,
Border,
0,
0,
Native.Width,
Native.Height,
Format,
Type,
Data);
NativeData);
}
}
@ -178,22 +173,24 @@ namespace Ryujinx.Graphics.Gal.OpenGL
public void Bind(long Key, int Index, GalImage Image)
{
if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
if (!TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
{
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
int[] SwizzleRgba = new int[]
{
(int)OGLEnumConverter.GetTextureSwizzle(Image.XSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.YSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.WSource)
};
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleRgba, SwizzleRgba);
throw new InvalidOperationException();
}
GL.ActiveTexture(TextureUnit.Texture0 + Index);
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
int[] SwizzleRgba = new int[]
{
(int)OGLEnumConverter.GetTextureSwizzle(Image.XSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.YSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource),
(int)OGLEnumConverter.GetTextureSwizzle(Image.WSource)
};
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleRgba, SwizzleRgba);
}
public void SetSampler(GalTextureSampler Sampler)
@ -220,5 +217,61 @@ namespace Ryujinx.Graphics.Gal.OpenGL
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color);
}
private static GalImage ConvertToNativeImage(GalImage Image)
{
GalImageFormat Format = Image.Format & GalImageFormat.FormatMask;
if (IsASTC(Image))
{
Image.Format = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm;
}
else if (Format == GalImageFormat.G8R8)
{
Image.Format = GalImageFormat.R8G8 | (Image.Format & GalImageFormat.TypeMask);
}
return Image;
}
private static byte[] ConvertToNativeData(GalImage Image, byte[] Data)
{
//TODO: Use KHR_texture_compression_astc_hdr when available
GalImageFormat Format = Image.Format & GalImageFormat.FormatMask;
if (IsASTC(Image))
{
int TextureBlockWidth = ImageUtils.GetBlockWidth(Image.Format);
int TextureBlockHeight = ImageUtils.GetBlockHeight(Image.Format);
return ASTCDecoder.DecodeToRGBA8888(
Data,
TextureBlockWidth,
TextureBlockHeight, 1,
Image.Width,
Image.Height, 1);
}
else if (Format == GalImageFormat.G8R8)
{
return ImageConverter.G8R8ToR8G8(
Data,
Image.Width,
Image.Height,
1);
}
else
{
return Data;
}
}
private static bool IsASTC(GalImage Image)
{
GalImageFormat Format = Image.Format & GalImageFormat.FormatMask;
return Format >= GalImageFormat.ASTC_BEGIN &&
Format <= GalImageFormat.ASTC_END;
}
}
}

View file

@ -37,13 +37,13 @@ namespace Ryujinx.Graphics
public void SendColorBuffer(NvGpuVmm Vmm, long Position, int Attachment, GalImage NewImage)
{
long Size = (uint)ImageUtils.GetSize(NewImage);
ImageTypes[Position] = ImageType.ColorBuffer;
if (!TryReuse(Vmm, Position, NewImage))
{
Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage);
int Size = ImageUtils.GetSize(NewImage);
Gpu.Renderer.Texture.CreateEmpty(Position, Size, NewImage);
}
Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage);
@ -51,13 +51,13 @@ namespace Ryujinx.Graphics
public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage)
{
long Size = (uint)ImageUtils.GetSize(NewImage);
ImageTypes[Position] = ImageType.ZetaBuffer;
if (!TryReuse(Vmm, Position, NewImage))
{
Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage);
int Size = ImageUtils.GetSize(NewImage);
Gpu.Renderer.Texture.CreateEmpty(Position, Size, NewImage);
}
Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage);
@ -102,7 +102,7 @@ namespace Ryujinx.Graphics
byte[] Data = ImageUtils.ReadTexture(Vmm, NewImage, Position);
Gpu.Renderer.Texture.Create(Position, Data, NewImage);
Gpu.Renderer.Texture.CreateData(Position, Data, NewImage);
}
private bool TryReuse(NvGpuVmm Vmm, long Position, GalImage NewImage)

View file

@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics
{
public interface ICompatible<T>
{
bool IsCompatible(T Other);
}
public class Resource
{
public int Timestamp { get; private set; }
public bool IsUsed { get; private set; }
public void UpdateStamp()
{
Timestamp = Environment.TickCount;
}
public void MarkAsUsed()
{
UpdateStamp();
IsUsed = true;
}
public void MarkAsUnused()
{
IsUsed = false;
}
}
class ResourcePool<TKey, TValue>
where TKey : ICompatible<TKey>
where TValue : Resource
{
private const int MaxTimeDelta = 5 * 60000;
private const int MaxRemovalsPerRun = 10;
public delegate TValue CreateValue(TKey Params);
public delegate void DeleteValue(TValue Resource);
private List<(TKey, List<TValue>)> Entries;
private Queue<(TValue, List<TValue>)> SortedCache;
private CreateValue CreateValueCallback;
private DeleteValue DeleteValueCallback;
public ResourcePool(CreateValue CreateValueCallback, DeleteValue DeleteValueCallback)
{
this.CreateValueCallback = CreateValueCallback;
this.DeleteValueCallback = DeleteValueCallback;
Entries = new List<(TKey, List<TValue>)>();
SortedCache = new Queue<(TValue, List<TValue>)>();
}
public TValue CreateOrRecycle(TKey Params)
{
List<TValue> Family = GetOrAddEntry(Params);
foreach (TValue RecycledValue in Family)
{
if (!RecycledValue.IsUsed)
{
RecycledValue.MarkAsUsed();
return RecycledValue;
}
}
TValue Resource = CreateValueCallback(Params);
Resource.MarkAsUsed();
Family.Add(Resource);
SortedCache.Enqueue((Resource, Family));
return Resource;
}
public void ReleaseMemory()
{
int Timestamp = Environment.TickCount;
for (int Count = 0; Count < MaxRemovalsPerRun; Count++)
{
if (!SortedCache.TryDequeue(out (TValue Resource, List<TValue> Family) Tuple))
{
break;
}
TValue Resource = Tuple.Resource;
List<TValue> Family = Tuple.Family;
if (!Resource.IsUsed)
{
int TimeDelta = RingDelta(Resource.Timestamp, Timestamp);
if ((uint)TimeDelta > MaxTimeDelta)
{
if (!Family.Remove(Resource))
{
throw new InvalidOperationException();
}
DeleteValueCallback(Resource);
continue;
}
}
SortedCache.Enqueue((Resource, Family));
}
}
private List<TValue> GetOrAddEntry(TKey Params)
{
foreach ((TKey MyParams, List<TValue> Resources) in Entries)
{
if (MyParams.IsCompatible(Params))
{
return Resources;
}
}
List<TValue> Family = new List<TValue>();
Entries.Add((Params, Family));
return Family;
}
private static int RingDelta(int Old, int New)
{
if ((uint)New < (uint)Old)
{
return New + (~Old + 1);
}
else
{
return New - Old;
}
}
}
}