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
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
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
|
||||
{
|
||||
class ImageHandler
|
||||
class ImageHandler : Resource
|
||||
{
|
||||
public GalImage Image { get; private set; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -3,7 +3,7 @@ using System;
|
|||
|
||||
namespace Ryujinx.Graphics.Gal.OpenGL
|
||||
{
|
||||
class OGLStreamBuffer : IDisposable
|
||||
class OGLStreamBuffer : Resource, IDisposable
|
||||
{
|
||||
public int Handle { get; protected set; }
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
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
Reference in a new issue