rjx-mirror/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs
jduncanator c1b7340023 Timing: Optimize Timestamp Aquisition (#479)
* Timing: Optimize Timestamp Aquisition

Currently, we make use of Environment.TickCount in a number of places. This has some downsides, mainly being that the TickCount is a signed 32-bit integer, and has an effective limit of ~25 days before overflowing and wrapping around. Due to the signed-ness of the value, this also caused issues with negative numbers. This resolves these issues by using a 64-bit tick count obtained from Performance Counters (via the Stopwatch class). This has a beneficial side effect of being significantly more accurate than the TickCount.

* Timing: Rename ElapsedTicks to ElapsedMilliseconds and expose TicksPerX

* Timing: Some style changes

* Timing: Align static variable initialization
2018-10-28 19:31:13 -03:00

175 lines
No EOL
4.2 KiB
C#

using Ryujinx.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLCachedResource<T>
{
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 long DataSize { get; private set; }
public long Timestamp { get; private set; }
public CacheBucket(T Value, long DataSize, LinkedListNode<long> Node)
{
this.Value = Value;
this.DataSize = DataSize;
this.Node = Node;
Timestamp = PerformanceCounter.ElapsedMilliseconds;
}
}
private Dictionary<long, CacheBucket> Cache;
private LinkedList<long> SortedCache;
private DeleteValue DeleteValueCallback;
private Queue<T> DeletePending;
private bool Locked;
public OGLCachedResource(DeleteValue DeleteValueCallback)
{
if (DeleteValueCallback == null)
{
throw new ArgumentNullException(nameof(DeleteValueCallback));
}
this.DeleteValueCallback = DeleteValueCallback;
Cache = new Dictionary<long, CacheBucket>();
SortedCache = new LinkedList<long>();
DeletePending = new Queue<T>();
}
public void Lock()
{
Locked = true;
}
public void Unlock()
{
Locked = false;
while (DeletePending.TryDequeue(out T Value))
{
DeleteValueCallback(Value);
}
ClearCacheIfNeeded();
}
public void AddOrUpdate(long Key, T Value, long Size)
{
if (!Locked)
{
ClearCacheIfNeeded();
}
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);
}
}
public bool TryGetValue(long Key, out T 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);
return true;
}
Value = default(T);
return false;
}
public bool TryGetSize(long Key, out long Size)
{
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
{
Size = Bucket.DataSize;
return true;
}
Size = 0;
return false;
}
private void ClearCacheIfNeeded()
{
long Timestamp = PerformanceCounter.ElapsedMilliseconds;
int Count = 0;
while (Count++ < MaxRemovalsPerRun)
{
LinkedListNode<long> Node = SortedCache.First;
if (Node == null)
{
break;
}
CacheBucket Bucket = Cache[Node.Value];
long TimeDelta = Bucket.Timestamp - Timestamp;
if ((uint)TimeDelta <= (uint)MaxTimeDelta)
{
break;
}
SortedCache.Remove(Node);
Cache.Remove(Node.Value);
DeleteValueCallback(Bucket.Value);
}
}
}
}