diff --git a/Ryujinx.Graphics/Gal/GalImage.cs b/Ryujinx.Graphics/Gal/GalImage.cs index 92f43cc9d4..649156f77a 100644 --- a/Ryujinx.Graphics/Gal/GalImage.cs +++ b/Ryujinx.Graphics/Gal/GalImage.cs @@ -2,7 +2,7 @@ using Ryujinx.Graphics.Texture; namespace Ryujinx.Graphics.Gal { - public struct GalImage + public struct GalImage : ICompatible { 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); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs index aeecdf1ac5..06b1ad0701 100644 --- a/Ryujinx.Graphics/Gal/IGalTexture.cs +++ b/Ryujinx.Graphics/Gal/IGalTexture.cs @@ -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); diff --git a/Ryujinx.Graphics/Gal/OpenGL/BufferParams.cs b/Ryujinx.Graphics/Gal/OpenGL/BufferParams.cs new file mode 100644 index 0000000000..137b720270 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/BufferParams.cs @@ -0,0 +1,24 @@ +using OpenTK.Graphics.OpenGL; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class BufferParams : ICompatible + { + 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; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs index 8db0b8a8c9..45fac0d706 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs @@ -2,7 +2,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { - class ImageHandler + class ImageHandler : Resource { public GalImage Image { get; private set; } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs index 839915eae4..3c74909f28 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs @@ -3,57 +3,46 @@ using System.Collections.Generic; namespace Ryujinx.Graphics.Gal.OpenGL { - class OGLCachedResource + class OGLCachedResource + where TKey : ICompatible + 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 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 Node) + public CacheBucket(TValue Value, long DataSize) { this.Value = Value; this.DataSize = DataSize; - this.Node = Node; - - Timestamp = Environment.TickCount; } } private Dictionary Cache; - private LinkedList SortedCache; - - private DeleteValue DeleteValueCallback; - - private Queue DeletePending; + private ResourcePool Pool; private bool Locked; - public OGLCachedResource(DeleteValue DeleteValueCallback) + public OGLCachedResource( + ResourcePool.CreateValue CreateValueCallback, + ResourcePool.DeleteValue DeleteValueCallback) { + if (CreateValueCallback == null) + { + throw new ArgumentNullException(nameof(CreateValueCallback)); + } + if (DeleteValueCallback == null) { throw new ArgumentNullException(nameof(DeleteValueCallback)); } - this.DeleteValueCallback = DeleteValueCallback; - Cache = new Dictionary(); - SortedCache = new LinkedList(); - - DeletePending = new Queue(); + Pool = new ResourcePool(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 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 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 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; - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs index 4958b53b3b..5639968271 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs @@ -5,11 +5,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLConstBuffer : IGalConstBuffer { - private OGLCachedResource Cache; + private OGLCachedResource Cache; public OGLConstBuffer() { - Cache = new OGLCachedResource(DeleteBuffer); + Cache = new OGLCachedResource(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(); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index a74aee0773..4bcf60b8e4 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -9,8 +9,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL private int[] VertexBuffers; - private OGLCachedResource VboCache; - private OGLCachedResource IboCache; + private OGLCachedResource VboCache; + private OGLCachedResource IboCache; private struct IbInfo { @@ -26,8 +26,8 @@ namespace Ryujinx.Graphics.Gal.OpenGL { VertexBuffers = new int[32]; - VboCache = new OGLCachedResource(GL.DeleteBuffer); - IboCache = new OGLCachedResource(GL.DeleteBuffer); + VboCache = new OGLCachedResource(CreateBuffer, DeleteBuffer); + IboCache = new OGLCachedResource(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(); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index ff5dc1b895..e408c9d43c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -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) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs index 946394059e..37af71ebbd 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs @@ -3,7 +3,7 @@ using System; namespace Ryujinx.Graphics.Gal.OpenGL { - class OGLStreamBuffer : IDisposable + class OGLStreamBuffer : Resource, IDisposable { public int Handle { get; protected set; } diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index 3347afbd1c..21b5d4597c 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -6,11 +6,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLTexture : IGalTexture { - private OGLCachedResource TextureCache; + private OGLCachedResource TextureCache; public OGLTexture() { - TextureCache = new OGLCachedResource(DeleteTexture); + TextureCache = new OGLCachedResource(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; + } } } diff --git a/Ryujinx.Graphics/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs index 0a8d201452..51934316b5 100644 --- a/Ryujinx.Graphics/GpuResourceManager.cs +++ b/Ryujinx.Graphics/GpuResourceManager.cs @@ -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) diff --git a/Ryujinx.Graphics/ResourcePool.cs b/Ryujinx.Graphics/ResourcePool.cs new file mode 100644 index 0000000000..48a426cbd6 --- /dev/null +++ b/Ryujinx.Graphics/ResourcePool.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics +{ + public interface ICompatible + { + 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 + where TKey : ICompatible + 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)> Entries; + + private Queue<(TValue, List)> SortedCache; + + private CreateValue CreateValueCallback; + private DeleteValue DeleteValueCallback; + + public ResourcePool(CreateValue CreateValueCallback, DeleteValue DeleteValueCallback) + { + this.CreateValueCallback = CreateValueCallback; + this.DeleteValueCallback = DeleteValueCallback; + + Entries = new List<(TKey, List)>(); + + SortedCache = new Queue<(TValue, List)>(); + } + + public TValue CreateOrRecycle(TKey Params) + { + List 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 Family) Tuple)) + { + break; + } + + TValue Resource = Tuple.Resource; + + List 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 GetOrAddEntry(TKey Params) + { + foreach ((TKey MyParams, List Resources) in Entries) + { + if (MyParams.IsCompatible(Params)) + { + return Resources; + } + } + + List Family = new List(); + + 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; + } + } + } +} \ No newline at end of file