Implement a conservative cache
This commit is contained in:
parent
7de7b559ad
commit
f88a7a8596
12 changed files with 428 additions and 248 deletions
|
@ -2,7 +2,7 @@ using Ryujinx.Graphics.Texture;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gal
|
namespace Ryujinx.Graphics.Gal
|
||||||
{
|
{
|
||||||
public struct GalImage
|
public struct GalImage : ICompatible<GalImage>
|
||||||
{
|
{
|
||||||
public int Width;
|
public int Width;
|
||||||
public int Height;
|
public int Height;
|
||||||
|
@ -59,5 +59,10 @@ namespace Ryujinx.Graphics.Gal
|
||||||
|
|
||||||
return Height == Image.Height;
|
return Height == Image.Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsCompatible(GalImage Other)
|
||||||
|
{
|
||||||
|
return Format == Other.Format && SizeMatches(Other);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,9 +5,9 @@ namespace Ryujinx.Graphics.Gal
|
||||||
void LockCache();
|
void LockCache();
|
||||||
void UnlockCache();
|
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);
|
bool TryGetImage(long Key, out GalImage Image);
|
||||||
|
|
||||||
|
|
24
Ryujinx.Graphics/Gal/OpenGL/BufferParams.cs
Normal file
24
Ryujinx.Graphics/Gal/OpenGL/BufferParams.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gal.OpenGL
|
namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
class ImageHandler
|
class ImageHandler : Resource
|
||||||
{
|
{
|
||||||
public GalImage Image { get; private set; }
|
public GalImage Image { get; private set; }
|
||||||
|
|
||||||
|
|
|
@ -3,57 +3,46 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gal.OpenGL
|
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
|
private struct CacheBucket
|
||||||
{
|
{
|
||||||
public T Value { get; private set; }
|
public TValue Value { get; private set; }
|
||||||
|
|
||||||
public LinkedListNode<long> Node { get; private set; }
|
|
||||||
|
|
||||||
public long DataSize { get; private set; }
|
public long DataSize { get; private set; }
|
||||||
|
|
||||||
public int Timestamp { get; private set; }
|
public CacheBucket(TValue Value, long DataSize)
|
||||||
|
|
||||||
public CacheBucket(T Value, long DataSize, LinkedListNode<long> Node)
|
|
||||||
{
|
{
|
||||||
this.Value = Value;
|
this.Value = Value;
|
||||||
this.DataSize = DataSize;
|
this.DataSize = DataSize;
|
||||||
this.Node = Node;
|
|
||||||
|
|
||||||
Timestamp = Environment.TickCount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dictionary<long, CacheBucket> Cache;
|
private Dictionary<long, CacheBucket> Cache;
|
||||||
|
|
||||||
private LinkedList<long> SortedCache;
|
private ResourcePool<TKey, TValue> Pool;
|
||||||
|
|
||||||
private DeleteValue DeleteValueCallback;
|
|
||||||
|
|
||||||
private Queue<T> DeletePending;
|
|
||||||
|
|
||||||
private bool Locked;
|
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)
|
if (DeleteValueCallback == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(DeleteValueCallback));
|
throw new ArgumentNullException(nameof(DeleteValueCallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.DeleteValueCallback = DeleteValueCallback;
|
|
||||||
|
|
||||||
Cache = new Dictionary<long, CacheBucket>();
|
Cache = new Dictionary<long, CacheBucket>();
|
||||||
|
|
||||||
SortedCache = new LinkedList<long>();
|
Pool = new ResourcePool<TKey, TValue>(CreateValueCallback, DeleteValueCallback);
|
||||||
|
|
||||||
DeletePending = new Queue<T>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Lock()
|
public void Lock()
|
||||||
|
@ -65,62 +54,40 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
Locked = false;
|
Locked = false;
|
||||||
|
|
||||||
while (DeletePending.TryDequeue(out T Value))
|
Pool.ReleaseMemory();
|
||||||
{
|
|
||||||
DeleteValueCallback(Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ClearCacheIfNeeded();
|
public TValue CreateOrRecycle(long Key, TKey Parameters, long Size)
|
||||||
}
|
|
||||||
|
|
||||||
public void AddOrUpdate(long Key, T Value, long Size)
|
|
||||||
{
|
{
|
||||||
if (!Locked)
|
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 (Cache.TryGetValue(Key, out CacheBucket Bucket))
|
||||||
{
|
{
|
||||||
if (Locked)
|
Bucket.Value.MarkAsUnused();
|
||||||
{
|
|
||||||
DeletePending.Enqueue(Bucket.Value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DeleteValueCallback(Bucket.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SortedCache.Remove(Bucket.Node);
|
TValue Value = Pool.CreateOrRecycle(Parameters);
|
||||||
|
|
||||||
Cache[Key] = NewBucket;
|
Cache[Key] = new CacheBucket(Value, Size);
|
||||||
}
|
|
||||||
else
|
return Value;
|
||||||
{
|
|
||||||
Cache.Add(Key, NewBucket);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetValue(long Key, out T Value)
|
public bool TryGetValue(long Key, out TValue Value)
|
||||||
{
|
{
|
||||||
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
|
if (Cache.TryGetValue(Key, out CacheBucket Bucket))
|
||||||
{
|
{
|
||||||
Value = Bucket.Value;
|
Value = Bucket.Value;
|
||||||
|
|
||||||
SortedCache.Remove(Bucket.Node);
|
Value.UpdateStamp();
|
||||||
|
|
||||||
LinkedListNode<long> Node = SortedCache.AddLast(Key);
|
|
||||||
|
|
||||||
Cache[Key] = new CacheBucket(Value, Bucket.DataSize, Node);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value = default(T);
|
Value = default(TValue);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -138,49 +105,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
return false;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,11 +5,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
class OGLConstBuffer : IGalConstBuffer
|
class OGLConstBuffer : IGalConstBuffer
|
||||||
{
|
{
|
||||||
private OGLCachedResource<OGLStreamBuffer> Cache;
|
private OGLCachedResource<BufferParams, OGLStreamBuffer> Cache;
|
||||||
|
|
||||||
public OGLConstBuffer()
|
public OGLConstBuffer()
|
||||||
{
|
{
|
||||||
Cache = new OGLCachedResource<OGLStreamBuffer>(DeleteBuffer);
|
Cache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LockCache()
|
public void LockCache()
|
||||||
|
@ -24,9 +24,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
public void Create(long Key, long Size)
|
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)
|
public bool IsCached(long Key, long Size)
|
||||||
|
@ -56,6 +56,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static OGLStreamBuffer CreateBuffer(BufferParams Params)
|
||||||
|
{
|
||||||
|
return new OGLStreamBuffer(Params.Target, Params.Size);
|
||||||
|
}
|
||||||
|
|
||||||
private static void DeleteBuffer(OGLStreamBuffer Buffer)
|
private static void DeleteBuffer(OGLStreamBuffer Buffer)
|
||||||
{
|
{
|
||||||
Buffer.Dispose();
|
Buffer.Dispose();
|
||||||
|
|
|
@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
private int[] VertexBuffers;
|
private int[] VertexBuffers;
|
||||||
|
|
||||||
private OGLCachedResource<int> VboCache;
|
private OGLCachedResource<BufferParams, OGLStreamBuffer> VboCache;
|
||||||
private OGLCachedResource<int> IboCache;
|
private OGLCachedResource<BufferParams, OGLStreamBuffer> IboCache;
|
||||||
|
|
||||||
private struct IbInfo
|
private struct IbInfo
|
||||||
{
|
{
|
||||||
|
@ -26,8 +26,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
VertexBuffers = new int[32];
|
VertexBuffers = new int[32];
|
||||||
|
|
||||||
VboCache = new OGLCachedResource<int>(GL.DeleteBuffer);
|
VboCache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
|
||||||
IboCache = new OGLCachedResource<int>(GL.DeleteBuffer);
|
IboCache = new OGLCachedResource<BufferParams, OGLStreamBuffer>(CreateBuffer, DeleteBuffer);
|
||||||
|
|
||||||
IndexBuffer = new IbInfo();
|
IndexBuffer = new IbInfo();
|
||||||
|
|
||||||
|
@ -97,26 +97,28 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
public void CreateVbo(long Key, int DataSize, IntPtr HostAddress)
|
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);
|
IntPtr Length = new IntPtr(DataSize);
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
|
GL.BindBuffer(BufferTarget.ArrayBuffer, CachedBuffer.Handle);
|
||||||
GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
|
|
||||||
|
GL.BufferSubData(BufferTarget.ArrayBuffer, IntPtr.Zero, Length, HostAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CreateIbo(long Key, int DataSize, IntPtr 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);
|
IntPtr Length = new IntPtr(DataSize);
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
|
GL.BindBuffer(BufferTarget.ElementArrayBuffer, CachedBuffer.Handle);
|
||||||
GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
|
|
||||||
|
GL.BufferSubData(BufferTarget.ElementArrayBuffer, IntPtr.Zero, Length, HostAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetIndexArray(int Size, GalIndexFormat Format)
|
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)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType);
|
PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType);
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.ElementArrayBuffer, IboHandle);
|
GL.BindBuffer(BufferTarget.ElementArrayBuffer, CachedBuffer.Handle);
|
||||||
|
|
||||||
First <<= IndexBuffer.ElemSizeLog2;
|
First <<= IndexBuffer.ElemSizeLog2;
|
||||||
|
|
||||||
|
@ -165,7 +167,26 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
public bool TryGetVbo(long VboKey, out int VboHandle)
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -382,11 +382,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyPBO);
|
Texture.CreatePBO(Key, ImageUtils.GetSize(NewImage), NewImage, CopyPBO);
|
||||||
|
|
||||||
Texture.Create(Key, ImageUtils.GetSize(NewImage), NewImage);
|
|
||||||
|
|
||||||
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static FramebufferAttachment GetAttachment(ImageHandler CachedImage)
|
private static FramebufferAttachment GetAttachment(ImageHandler CachedImage)
|
||||||
|
|
|
@ -3,7 +3,7 @@ using System;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gal.OpenGL
|
namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
class OGLStreamBuffer : IDisposable
|
class OGLStreamBuffer : Resource, IDisposable
|
||||||
{
|
{
|
||||||
public int Handle { get; protected set; }
|
public int Handle { get; protected set; }
|
||||||
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
{
|
{
|
||||||
class OGLTexture : IGalTexture
|
class OGLTexture : IGalTexture
|
||||||
{
|
{
|
||||||
private OGLCachedResource<ImageHandler> TextureCache;
|
private OGLCachedResource<GalImage, ImageHandler> TextureCache;
|
||||||
|
|
||||||
public OGLTexture()
|
public OGLTexture()
|
||||||
{
|
{
|
||||||
TextureCache = new OGLCachedResource<ImageHandler>(DeleteTexture);
|
TextureCache = new OGLCachedResource<GalImage, ImageHandler>(CreateTexture, DeleteTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LockCache()
|
public void LockCache()
|
||||||
|
@ -23,130 +23,125 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
TextureCache.Unlock();
|
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)
|
private static void DeleteTexture(ImageHandler CachedImage)
|
||||||
{
|
{
|
||||||
GL.DeleteTexture(CachedImage.Handle);
|
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();
|
TextureCache.CreateOrRecycle(Key, Image, Size);
|
||||||
|
|
||||||
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(
|
public void CreatePBO(long Key, int Size, GalImage Image, int PBO)
|
||||||
|
{
|
||||||
|
const int Level = 0; //TODO: Support mipmap textures.
|
||||||
|
|
||||||
|
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,
|
TextureTarget.Texture2D,
|
||||||
Level,
|
Level,
|
||||||
InternalFmt,
|
0,
|
||||||
Image.Width,
|
0,
|
||||||
Image.Height,
|
Native.Width,
|
||||||
Border,
|
Native.Height,
|
||||||
Format,
|
Format,
|
||||||
Type,
|
Type,
|
||||||
IntPtr.Zero);
|
IntPtr.Zero);
|
||||||
}
|
|
||||||
|
GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Create(long Key, byte[] Data, GalImage Image)
|
public void CreateData(long Key, byte[] Data, GalImage Image)
|
||||||
{
|
{
|
||||||
int Handle = GL.GenTexture();
|
|
||||||
|
|
||||||
GL.BindTexture(TextureTarget.Texture2D, Handle);
|
|
||||||
|
|
||||||
const int Level = 0; //TODO: Support mipmap textures.
|
const int Level = 0; //TODO: Support mipmap textures.
|
||||||
const int Border = 0;
|
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(
|
GL.CompressedTexImage2D(
|
||||||
TextureTarget.Texture2D,
|
TextureTarget.Texture2D,
|
||||||
Level,
|
Level,
|
||||||
InternalFmt,
|
InternalFmt,
|
||||||
Image.Width,
|
Native.Width,
|
||||||
Image.Height,
|
Native.Height,
|
||||||
Border,
|
Border,
|
||||||
Data.Length,
|
Data.Length,
|
||||||
Data);
|
NativeData);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//TODO: Use KHR_texture_compression_astc_hdr when available
|
(_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Native.Format);
|
||||||
if (IsASTC)
|
|
||||||
{
|
|
||||||
int TextureBlockWidth = ImageUtils.GetBlockWidth(Image.Format);
|
|
||||||
int TextureBlockHeight = ImageUtils.GetBlockHeight(Image.Format);
|
|
||||||
|
|
||||||
Data = ASTCDecoder.DecodeToRGBA8888(
|
GL.TexSubImage2D(
|
||||||
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(
|
|
||||||
TextureTarget.Texture2D,
|
TextureTarget.Texture2D,
|
||||||
Level,
|
Level,
|
||||||
InternalFmt,
|
0,
|
||||||
Image.Width,
|
0,
|
||||||
Image.Height,
|
Native.Width,
|
||||||
Border,
|
Native.Height,
|
||||||
Format,
|
Format,
|
||||||
Type,
|
Type,
|
||||||
Data);
|
NativeData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -178,8 +173,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
public void Bind(long Key, int Index, GalImage Image)
|
public void Bind(long Key, int Index, GalImage Image)
|
||||||
{
|
{
|
||||||
if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
|
if (!TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
|
||||||
{
|
{
|
||||||
|
throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
GL.ActiveTexture(TextureUnit.Texture0 + Index);
|
GL.ActiveTexture(TextureUnit.Texture0 + Index);
|
||||||
|
|
||||||
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
|
GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
|
||||||
|
@ -194,7 +192,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleRgba, SwizzleRgba);
|
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleRgba, SwizzleRgba);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public void SetSampler(GalTextureSampler Sampler)
|
public void SetSampler(GalTextureSampler Sampler)
|
||||||
{
|
{
|
||||||
|
@ -220,5 +217,61 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
||||||
|
|
||||||
GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,13 @@ namespace Ryujinx.Graphics
|
||||||
|
|
||||||
public void SendColorBuffer(NvGpuVmm Vmm, long Position, int Attachment, GalImage NewImage)
|
public void SendColorBuffer(NvGpuVmm Vmm, long Position, int Attachment, GalImage NewImage)
|
||||||
{
|
{
|
||||||
long Size = (uint)ImageUtils.GetSize(NewImage);
|
|
||||||
|
|
||||||
ImageTypes[Position] = ImageType.ColorBuffer;
|
ImageTypes[Position] = ImageType.ColorBuffer;
|
||||||
|
|
||||||
if (!TryReuse(Vmm, Position, NewImage))
|
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);
|
Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage);
|
||||||
|
@ -51,13 +51,13 @@ namespace Ryujinx.Graphics
|
||||||
|
|
||||||
public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage)
|
public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage)
|
||||||
{
|
{
|
||||||
long Size = (uint)ImageUtils.GetSize(NewImage);
|
|
||||||
|
|
||||||
ImageTypes[Position] = ImageType.ZetaBuffer;
|
ImageTypes[Position] = ImageType.ZetaBuffer;
|
||||||
|
|
||||||
if (!TryReuse(Vmm, Position, NewImage))
|
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);
|
Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage);
|
||||||
|
@ -102,7 +102,7 @@ namespace Ryujinx.Graphics
|
||||||
|
|
||||||
byte[] Data = ImageUtils.ReadTexture(Vmm, NewImage, Position);
|
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)
|
private bool TryReuse(NvGpuVmm Vmm, long Position, GalImage NewImage)
|
||||||
|
|
153
Ryujinx.Graphics/ResourcePool.cs
Normal file
153
Ryujinx.Graphics/ResourcePool.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue