diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs index 31b648df38..dec6d3971f 100644 --- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs +++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs @@ -2,10 +2,10 @@ namespace Ryujinx.Graphics.Gal { public struct GalVertexAttrib { - public int Index { get; private set; } - public bool IsConst { get; private set; } - public int Offset { get; private set; } - public byte[] Data { get; private set; } + public bool IsConst { get; private set; } + public int ArrayIndex { get; private set; } + public int Offset { get; private set; } + public byte[] Data { get; private set; } public GalVertexAttribSize Size { get; private set; } public GalVertexAttribType Type { get; private set; } @@ -13,21 +13,21 @@ namespace Ryujinx.Graphics.Gal public bool IsBgra { get; private set; } public GalVertexAttrib( - int Index, bool IsConst, + int ArrayIndex, int Offset, byte[] Data, GalVertexAttribSize Size, GalVertexAttribType Type, bool IsBgra) { - this.Index = Index; - this.IsConst = IsConst; - this.Data = Data; - this.Offset = Offset; - this.Size = Size; - this.Type = Type; - this.IsBgra = IsBgra; + this.IsConst = IsConst; + this.Data = Data; + this.ArrayIndex = ArrayIndex; + this.Offset = Offset; + this.Size = Size; + this.Type = Type; + this.IsBgra = IsBgra; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/GalVertexAttribArray.cs b/Ryujinx.Graphics/Gal/GalVertexAttribArray.cs new file mode 100644 index 0000000000..6ff69ab48f --- /dev/null +++ b/Ryujinx.Graphics/Gal/GalVertexAttribArray.cs @@ -0,0 +1,43 @@ +using System; + +namespace Ryujinx.Graphics.Gal +{ + public struct GalVertexAttribArray : IEquatable + { + public bool Enabled { get; private set; } + public long VboKey { get; private set; } + public int Stride { get; private set; } + public int Divisor { get; private set; } + + public GalVertexAttribArray(long vboKey, int stride, int divisor) + { + Enabled = true; + VboKey = vboKey; + Stride = stride; + Divisor = divisor; + } + + public override bool Equals(object obj) + { + if (!(obj is GalVertexAttribArray array)) + { + return false; + } + + return Equals(array); + } + + public bool Equals(GalVertexAttribArray array) + { + return Enabled == array.Enabled && + VboKey == array.VboKey && + Stride == array.Stride && + Divisor == array.Divisor; + } + + public override int GetHashCode() + { + return HashCode.Combine(Enabled, VboKey, Stride, Divisor); + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs b/Ryujinx.Graphics/Gal/IGalConstBuffer.cs index 0cdcc2371f..3066649860 100644 --- a/Ryujinx.Graphics/Gal/IGalConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/IGalConstBuffer.cs @@ -7,11 +7,9 @@ namespace Ryujinx.Graphics.Gal void LockCache(); void UnlockCache(); - void Create(long Key, long Size); + void Create(long key, IntPtr hostAddress, int size); + void Create(long key, byte[] buffer); - bool IsCached(long Key, long Size); - - void SetData(long Key, long Size, IntPtr HostAddress); - void SetData(long Key, byte[] Data); + bool IsCached(long Key, int Size); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs index 04f7aae50a..7b4ad959dd 100644 --- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs +++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs @@ -17,15 +17,21 @@ namespace Ryujinx.Graphics.Gal float Depth, int Stencil); - bool IsVboCached(long Key, long DataSize); + bool TryBindVao(ReadOnlySpan rawAttributes, GalVertexAttribArray[] arrays); - bool IsIboCached(long Key, long DataSize); + void CreateVao( + ReadOnlySpan rawAttributes, + GalVertexAttrib[] attributes, + GalVertexAttribArray[] arrays); - void CreateVbo(long Key, int DataSize, IntPtr HostAddress); - void CreateVbo(long Key, byte[] Data); + bool IsVboCached(long key, int size); + bool IsIboCached(long key, int size, out long vertexCount); - void CreateIbo(long Key, int DataSize, IntPtr HostAddress); - void CreateIbo(long Key, int DataSize, byte[] Buffer); + void CreateVbo(long key, IntPtr hostAddress, int size); + void CreateIbo(long key, IntPtr hostAddress, int size, long vertexCount); + + void CreateVbo(long key, byte[] buffer); + void CreateIbo(long key, byte[] buffer, long vertexCount); void SetIndexArray(int Size, GalIndexFormat Format); diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs index 626a415516..c5d6cce475 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs @@ -37,7 +37,5 @@ namespace Ryujinx.Graphics.Gal int DstY0, int DstX1, int DstY1); - - void Reinterpret(long Key, GalImage NewImage); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/IGalShader.cs b/Ryujinx.Graphics/Gal/IGalShader.cs index 4b951fa611..477767dea1 100644 --- a/Ryujinx.Graphics/Gal/IGalShader.cs +++ b/Ryujinx.Graphics/Gal/IGalShader.cs @@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Gal IEnumerable GetConstBufferUsage(long Key); IEnumerable GetTextureUsage(long Key); + void SetExtraData(float FlipX, float FlipY, int Instance); + void Bind(long Key); void Unbind(GalShaderType Type); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs deleted file mode 100644 index 6e17872ba0..0000000000 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs +++ /dev/null @@ -1,191 +0,0 @@ -using Ryujinx.Common; -using System; -using System.Collections.Generic; - -namespace Ryujinx.Graphics.Gal.OpenGL -{ - class OGLCachedResource - { - public delegate void DeleteValue(T Value); - - private const int MinTimeDelta = 5 * 60000; - private const int MaxRemovalsPerRun = 10; - - private struct CacheBucket - { - public T Value { get; private set; } - - public LinkedListNode Node { get; private set; } - - public long DataSize { get; private set; } - - public long Timestamp { get; private set; } - - public CacheBucket(T Value, long DataSize, LinkedListNode Node) - { - this.Value = Value; - this.DataSize = DataSize; - this.Node = Node; - - Timestamp = PerformanceCounter.ElapsedMilliseconds; - } - } - - private Dictionary Cache; - - private LinkedList SortedCache; - - private DeleteValue DeleteValueCallback; - - private Queue DeletePending; - - private bool Locked; - - private long MaxSize; - private long TotalSize; - - public OGLCachedResource(DeleteValue DeleteValueCallback, long MaxSize) - { - this.MaxSize = MaxSize; - - if (DeleteValueCallback == null) - { - throw new ArgumentNullException(nameof(DeleteValueCallback)); - } - - this.DeleteValueCallback = DeleteValueCallback; - - Cache = new Dictionary(); - - SortedCache = new LinkedList(); - - DeletePending = new Queue(); - } - - 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 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); - - TotalSize -= Bucket.DataSize; - - Cache[Key] = NewBucket; - } - else - { - Cache.Add(Key, NewBucket); - } - - TotalSize += Size; - } - - public bool TryGetValue(long Key, out T 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); - - 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 Node = SortedCache.First; - - if (Node == null) - { - break; - } - - CacheBucket Bucket = Cache[Node.Value]; - - long TimeDelta = Timestamp - Bucket.Timestamp; - - if (TimeDelta <= MinTimeDelta && !UnderMemoryPressure()) - { - break; - } - - SortedCache.Remove(Node); - - Cache.Remove(Node.Value); - - DeleteValueCallback(Bucket.Value); - - TotalSize -= Bucket.DataSize; - } - } - - private bool UnderMemoryPressure() - { - return TotalSize >= MaxSize; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs index 6fd8776b08..854b34424a 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs @@ -1,16 +1,20 @@ -using OpenTK.Graphics.OpenGL; +using OpenTK.Graphics.OpenGL; using System; namespace Ryujinx.Graphics.Gal.OpenGL { class OGLConstBuffer : IGalConstBuffer { - - private OGLCachedResource Cache; + private OGLResourceCache Cache; public OGLConstBuffer() { - Cache = new OGLCachedResource(DeleteBuffer, OGLResourceLimits.ConstBufferLimit); + Cache = new OGLResourceCache(DeleteBuffer, OGLResourceLimits.ConstBufferLimit); + } + + private static void DeleteBuffer(OGLStreamBuffer Buffer) + { + Buffer.Dispose(); } public void LockCache() @@ -23,51 +27,45 @@ namespace Ryujinx.Graphics.Gal.OpenGL Cache.Unlock(); } - public void Create(long Key, long Size) + public bool IsCached(long key, int size) { - OGLStreamBuffer Buffer = new OGLStreamBuffer(BufferTarget.UniformBuffer, Size); - - Cache.AddOrUpdate(Key, Buffer, Size); + return Cache.TryGetSize(key, out int cbSize) && cbSize >= size; } - public bool IsCached(long Key, long Size) + public void Create(long key, IntPtr hostAddress, int size) { - return Cache.TryGetSize(Key, out long CachedSize) && CachedSize == Size; + GetBuffer(key, size).SetData(hostAddress, size); } - public void SetData(long Key, long Size, IntPtr HostAddress) + public void Create(long key, byte[] buffer) { - if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) + GetBuffer(key, buffer.Length).SetData(buffer); + } + + public bool TryGetUbo(long key, out int uboHandle) + { + if (Cache.TryGetValue(key, out OGLStreamBuffer buffer)) { - Buffer.SetData(Size, HostAddress); - } - } - - public void SetData(long Key, byte[] Data) - { - if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) - { - Buffer.SetData(Data); - } - } - - public bool TryGetUbo(long Key, out int UboHandle) - { - if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer)) - { - UboHandle = Buffer.Handle; + uboHandle = buffer.Handle; return true; } - UboHandle = 0; + uboHandle = 0; return false; } - private static void DeleteBuffer(OGLStreamBuffer Buffer) + private OGLStreamBuffer GetBuffer(long Key, int Size) { - Buffer.Dispose(); + if (!Cache.TryReuseValue(Key, Size, out OGLStreamBuffer Buffer)) + { + Buffer = new OGLStreamBuffer(BufferTarget.UniformBuffer, Size); + + Cache.AddOrUpdate(Key, Size, Buffer, Size); + } + + return Buffer; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index c4015d020c..ada821492d 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -1,17 +1,116 @@ using OpenTK.Graphics.OpenGL; using System; +using System.Collections.Generic; namespace Ryujinx.Graphics.Gal.OpenGL { class OGLRasterizer : IGalRasterizer { - private const long MaxVertexBufferCacheSize = 128 * 1024 * 1024; - private const long MaxIndexBufferCacheSize = 64 * 1024 * 1024; + private static Dictionary AttribElements = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, 4 }, + { GalVertexAttribSize._32_32_32, 3 }, + { GalVertexAttribSize._16_16_16_16, 4 }, + { GalVertexAttribSize._32_32, 2 }, + { GalVertexAttribSize._16_16_16, 3 }, + { GalVertexAttribSize._8_8_8_8, 4 }, + { GalVertexAttribSize._16_16, 2 }, + { GalVertexAttribSize._32, 1 }, + { GalVertexAttribSize._8_8_8, 3 }, + { GalVertexAttribSize._8_8, 2 }, + { GalVertexAttribSize._16, 1 }, + { GalVertexAttribSize._8, 1 }, + { GalVertexAttribSize._10_10_10_2, 4 }, + { GalVertexAttribSize._11_11_10, 3 } + }; + + private static Dictionary FloatAttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.HalfFloat }, + { GalVertexAttribSize._32, VertexAttribPointerType.Float }, + { GalVertexAttribSize._16, VertexAttribPointerType.HalfFloat } + }; + + private static Dictionary SignedAttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._32, VertexAttribPointerType.Int }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._16, VertexAttribPointerType.Short }, + { GalVertexAttribSize._8, VertexAttribPointerType.Byte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.Int2101010Rev } + }; + + private static Dictionary UnsignedAttribTypes = + new Dictionary() + { + { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._32_32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._32_32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._16_16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._8_8_8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._16_16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._32, VertexAttribPointerType.UnsignedInt }, + { GalVertexAttribSize._8_8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._8_8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._16, VertexAttribPointerType.UnsignedShort }, + { GalVertexAttribSize._8, VertexAttribPointerType.UnsignedByte }, + { GalVertexAttribSize._10_10_10_2, VertexAttribPointerType.UnsignedInt2101010Rev }, + { GalVertexAttribSize._11_11_10, VertexAttribPointerType.UnsignedInt10F11F11FRev } + }; private int[] VertexBuffers; - private OGLCachedResource VboCache; - private OGLCachedResource IboCache; + private struct CachedVao + { + public int[] Attributes { get; } + + public GalVertexAttribArray[] Arrays { get; } + + public int Handle { get; } + + public CachedVao(int[] attributes, GalVertexAttribArray[] arrays) + { + Attributes = attributes; + Arrays = arrays; + + Handle = GL.GenVertexArray(); + } + } + + private OGLResourceCache VaoCache; + + private OGLResourceCache VboCache; + + private class CachedIbo + { + public OGLStreamBuffer Buffer { get; } + + public long VertexCount { get; set; } + + public CachedIbo(OGLStreamBuffer buffer) + { + Buffer = buffer; + } + } + + private OGLResourceCache IboCache; private struct IbInfo { @@ -27,33 +126,53 @@ namespace Ryujinx.Graphics.Gal.OpenGL { VertexBuffers = new int[32]; - VboCache = new OGLCachedResource(GL.DeleteBuffer, MaxVertexBufferCacheSize); - IboCache = new OGLCachedResource(GL.DeleteBuffer, MaxIndexBufferCacheSize); + VaoCache = new OGLResourceCache(DeleteVao, OGLResourceLimits.VertexArrayLimit); + + VboCache = new OGLResourceCache(DeleteBuffer, OGLResourceLimits.VertexBufferLimit); + + IboCache = new OGLResourceCache(DeleteIbo, OGLResourceLimits.IndexBufferLimit); IndexBuffer = new IbInfo(); } + private static void DeleteVao(CachedVao vao) + { + GL.DeleteVertexArray(vao.Handle); + } + + private static void DeleteBuffer(OGLStreamBuffer buffer) + { + buffer.Dispose(); + } + + private static void DeleteIbo(CachedIbo ibo) + { + ibo.Buffer.Dispose(); + } + public void LockCaches() { + VaoCache.Lock(); VboCache.Lock(); IboCache.Lock(); } public void UnlockCaches() { + VaoCache.Unlock(); VboCache.Unlock(); IboCache.Unlock(); } public void ClearBuffers( GalClearBufferFlags Flags, - int Attachment, - float Red, - float Green, - float Blue, - float Alpha, - float Depth, - int Stencil) + int Attachment, + float Red, + float Green, + float Blue, + float Alpha, + float Depth, + int Stencil) { GL.ColorMask( Attachment, @@ -78,62 +197,347 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } - public bool IsVboCached(long Key, long DataSize) + public bool IsVboCached(long key, int size) { - return VboCache.TryGetSize(Key, out long Size) && Size == DataSize; + return VboCache.TryGetSize(key, out int vbSize) && vbSize >= size; } - public bool IsIboCached(long Key, long DataSize) + public bool IsIboCached(long key, int size, out long vertexCount) { - return IboCache.TryGetSize(Key, out long Size) && Size == DataSize; + if (IboCache.TryGetSizeAndValue(key, out int ibSize, out CachedIbo ibo) && ibSize >= size) + { + vertexCount = ibo.VertexCount; + + return true; + } + + vertexCount = 0; + + return false; } - public void CreateVbo(long Key, int DataSize, IntPtr HostAddress) + public bool TryBindVao(ReadOnlySpan rawAttributes, GalVertexAttribArray[] arrays) { - int Handle = GL.GenBuffer(); + long hash = CalculateHash(arrays); - VboCache.AddOrUpdate(Key, Handle, DataSize); + if (!VaoCache.TryGetValue(hash, out CachedVao vao)) + { + return false; + } - IntPtr Length = new IntPtr(DataSize); + if (rawAttributes.Length != vao.Attributes.Length) + { + return false; + } - GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); + if (arrays.Length != vao.Arrays.Length) + { + return false; + } + + for (int index = 0; index < rawAttributes.Length; index++) + { + if (rawAttributes[index] != vao.Attributes[index]) + { + return false; + } + } + + for (int index = 0; index < arrays.Length; index++) + { + if (!arrays[index].Equals(vao.Arrays[index])) + { + return false; + } + } + + GL.BindVertexArray(vao.Handle); + + return true; } - public void CreateVbo(long Key, byte[] Data) + public void CreateVao( + ReadOnlySpan rawAttributes, + GalVertexAttrib[] attributes, + GalVertexAttribArray[] arrays) { - int Handle = GL.GenBuffer(); + CachedVao vao = new CachedVao(rawAttributes.ToArray(), arrays); - VboCache.AddOrUpdate(Key, Handle, Data.Length); + long hash = CalculateHash(arrays); - IntPtr Length = new IntPtr(Data.Length); + VaoCache.AddOrUpdate(hash, 1, vao, 1); - GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); - GL.BufferData(BufferTarget.ArrayBuffer, Length, Data, BufferUsageHint.StreamDraw); + GL.BindVertexArray(vao.Handle); + + for (int index = 0; index < attributes.Length; index++) + { + GalVertexAttrib attrib = attributes[index]; + + GalVertexAttribArray array = arrays[attrib.ArrayIndex]; + + //Skip uninitialized attributes. + if (attrib.Size == 0 || !array.Enabled) + { + continue; + } + + if (!VboCache.TryGetValue(array.VboKey, out OGLStreamBuffer vbo)) + { + continue; + } + + VboCache.AddDependency(array.VboKey, VaoCache, hash); + + GL.BindBuffer(BufferTarget.ArrayBuffer, vbo.Handle); + + bool Unsigned = + attrib.Type == GalVertexAttribType.Unorm || + attrib.Type == GalVertexAttribType.Uint || + attrib.Type == GalVertexAttribType.Uscaled; + + bool Normalize = + attrib.Type == GalVertexAttribType.Snorm || + attrib.Type == GalVertexAttribType.Unorm; + + VertexAttribPointerType Type = 0; + + if (attrib.Type == GalVertexAttribType.Float) + { + Type = GetType(FloatAttribTypes, attrib); + } + else if (Unsigned) + { + Type = GetType(UnsignedAttribTypes, attrib); + } + else + { + Type = GetType(SignedAttribTypes, attrib); + } + + if (!AttribElements.TryGetValue(attrib.Size, out int Size)) + { + throw new InvalidOperationException($"Invalid attribute size \"{attrib.Size}\"."); + } + + int Offset = attrib.Offset; + + if (array.Stride != 0) + { + GL.EnableVertexAttribArray(index); + + if (attrib.Type == GalVertexAttribType.Sint || + attrib.Type == GalVertexAttribType.Uint) + { + IntPtr Pointer = new IntPtr(Offset); + + VertexAttribIntegerType IType = (VertexAttribIntegerType)Type; + + GL.VertexAttribIPointer(index, Size, IType, array.Stride, Pointer); + } + else + { + GL.VertexAttribPointer(index, Size, Type, Normalize, array.Stride, Offset); + } + } + else + { + GL.DisableVertexAttribArray(index); + + SetConstAttrib(attrib, (uint)index); + } + + if (array.Divisor != 0) + { + GL.VertexAttribDivisor(index, 1); + } + else + { + GL.VertexAttribDivisor(index, 0); + } + } } - public void CreateIbo(long Key, int DataSize, IntPtr HostAddress) + private long CalculateHash(GalVertexAttribArray[] arrays) { - int Handle = GL.GenBuffer(); + if (arrays.Length == 1) + { + return arrays[0].VboKey; + } - IboCache.AddOrUpdate(Key, Handle, (uint)DataSize); + long hash = 17; - IntPtr Length = new IntPtr(DataSize); + for (int index = 0; index < arrays.Length; index++) + { + hash = hash * 23 + arrays[index].VboKey; + } - GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw); + return hash; } - public void CreateIbo(long Key, int DataSize, byte[] Buffer) + private static VertexAttribPointerType GetType(Dictionary Dict, GalVertexAttrib Attrib) { - int Handle = GL.GenBuffer(); + if (!Dict.TryGetValue(Attrib.Size, out VertexAttribPointerType Type)) + { + ThrowUnsupportedAttrib(Attrib); + } - IboCache.AddOrUpdate(Key, Handle, DataSize); + return Type; + } - IntPtr Length = new IntPtr(Buffer.Length); + private unsafe static void SetConstAttrib(GalVertexAttrib Attrib, uint Index) + { + if (Attrib.Size == GalVertexAttribSize._10_10_10_2 || + Attrib.Size == GalVertexAttribSize._11_11_10) + { + ThrowUnsupportedAttrib(Attrib); + } - GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); - GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + fixed (byte* Ptr = Attrib.Data) + { + if (Attrib.Type == GalVertexAttribType.Unorm) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N(Index, Ptr); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N(Index, (ushort*)Ptr); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N(Index, (uint*)Ptr); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Snorm) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttrib4N(Index, (sbyte*)Ptr); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttrib4N(Index, (short*)Ptr); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4N(Index, (int*)Ptr); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Uint) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4(Index, Ptr); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4(Index, (ushort*)Ptr); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4(Index, (uint*)Ptr); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Sint) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._8: + case GalVertexAttribSize._8_8: + case GalVertexAttribSize._8_8_8: + case GalVertexAttribSize._8_8_8_8: + GL.VertexAttribI4(Index, (sbyte*)Ptr); + break; + + case GalVertexAttribSize._16: + case GalVertexAttribSize._16_16: + case GalVertexAttribSize._16_16_16: + case GalVertexAttribSize._16_16_16_16: + GL.VertexAttribI4(Index, (short*)Ptr); + break; + + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttribI4(Index, (int*)Ptr); + break; + } + } + else if (Attrib.Type == GalVertexAttribType.Float) + { + switch (Attrib.Size) + { + case GalVertexAttribSize._32: + case GalVertexAttribSize._32_32: + case GalVertexAttribSize._32_32_32: + case GalVertexAttribSize._32_32_32_32: + GL.VertexAttrib4(Index, (float*)Ptr); + break; + + default: ThrowUnsupportedAttrib(Attrib); break; + } + } + } + } + + private static void ThrowUnsupportedAttrib(GalVertexAttrib Attrib) + { + throw new NotImplementedException("Unsupported size \"" + Attrib.Size + "\" on type \"" + Attrib.Type + "\"!"); + } + + public void CreateVbo(long key, IntPtr hostAddress, int size) + { + GetVbo(key, size).SetData(hostAddress, size); + } + + public void CreateIbo(long key, IntPtr hostAddress, int size, long vertexCount) + { + GetIbo(key, size, vertexCount).SetData(hostAddress, size); + } + + public void CreateVbo(long key, byte[] buffer) + { + GetVbo(key, buffer.Length).SetData(buffer); + } + + public void CreateIbo(long key, byte[] buffer, long vertexCount) + { + GetIbo(key, buffer.Length, vertexCount).SetData(buffer); } public void SetIndexArray(int Size, GalIndexFormat Format) @@ -176,14 +580,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 CachedIbo Ibo)) { return; } PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType); - GL.BindBuffer(BufferTarget.ElementArrayBuffer, IboHandle); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, Ibo.Buffer.Handle); First <<= IndexBuffer.ElemSizeLog2; @@ -201,7 +605,42 @@ 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 Vbo)) + { + VboHandle = Vbo.Handle; + + return true; + } + + VboHandle = 0; + + return false; + } + + private OGLStreamBuffer GetVbo(long Key, int Size) + { + if (!VboCache.TryReuseValue(Key, Size, out OGLStreamBuffer Buffer)) + { + Buffer = new OGLStreamBuffer(BufferTarget.ArrayBuffer, Size); + + VboCache.AddOrUpdate(Key, Size, Buffer, Size); + } + + return Buffer; + } + + private OGLStreamBuffer GetIbo(long Key, int Size, long VertexCount) + { + if (!IboCache.TryReuseValue(Key, Size, out CachedIbo Ibo)) + { + OGLStreamBuffer Buffer = new OGLStreamBuffer(BufferTarget.ElementArrayBuffer, Size); + + IboCache.AddOrUpdate(Key, Size, Ibo = new CachedIbo(Buffer), Size); + } + + Ibo.VertexCount = VertexCount; + + return Ibo.Buffer; } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs index f5968e3ecd..54c7bf8393 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs @@ -1,5 +1,4 @@ using OpenTK.Graphics.OpenGL; -using Ryujinx.Graphics.Texture; using System; namespace Ryujinx.Graphics.Gal.OpenGL @@ -88,8 +87,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL private FrameBufferAttachments Attachments; private FrameBufferAttachments OldAttachments; - private int CopyPBO; - public bool FramebufferSrgb { get; set; } public OGLRenderTarget(OGLTexture Texture) @@ -440,56 +437,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } - public void Reinterpret(long Key, GalImage NewImage) - { - if (!Texture.TryGetImage(Key, out GalImage OldImage)) - { - return; - } - - if (NewImage.Format == OldImage.Format && - NewImage.Width == OldImage.Width && - NewImage.Height == OldImage.Height) - { - return; - } - - if (CopyPBO == 0) - { - CopyPBO = GL.GenBuffer(); - } - - GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyPBO); - - //The buffer should be large enough to hold the largest texture. - int BufferSize = Math.Max(ImageUtils.GetSize(OldImage), - ImageUtils.GetSize(NewImage)); - - GL.BufferData(BufferTarget.PixelPackBuffer, BufferSize, IntPtr.Zero, BufferUsageHint.StreamCopy); - - if (!Texture.TryGetImageHandler(Key, out ImageHandler CachedImage)) - { - throw new InvalidOperationException(); - } - - (_, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(CachedImage.Format); - - GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle); - - GL.GetTexImage(TextureTarget.Texture2D, 0, Format, Type, IntPtr.Zero); - - GL.BindBuffer(BufferTarget.PixelPackBuffer, 0); - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyPBO); - - GL.PixelStore(PixelStoreParameter.UnpackRowLength, OldImage.Width); - - Texture.Create(Key, ImageUtils.GetSize(NewImage), NewImage); - - GL.PixelStore(PixelStoreParameter.UnpackRowLength, 0); - - GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0); - } - private static FramebufferAttachment GetAttachment(ImageHandler CachedImage) { if (CachedImage.HasColor) diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLResourceCache.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLResourceCache.cs new file mode 100644 index 0000000000..4db98b43d8 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLResourceCache.cs @@ -0,0 +1,393 @@ +using Ryujinx.Common; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLResourceCache + { + private const int MinTimeDelta = 5 * 60000; + private const int MaxRemovalsPerRun = 10; + + private const int DefaultMinTimeForPoolTransfer = 2500; + + private class CacheBucket + { + public long Key { get; private set; } + public TPoolKey PoolKey { get; private set; } + public TValue Value { get; private set; } + + public LinkedListNode CacheNode { get; private set; } + public LinkedListNode PoolNode { get; private set; } + + private Queue _deleteDeps; + + public int Size { get; private set; } + + public long Timestamp { get; private set; } + + public bool Orphan { get; private set; } + + public CacheBucket(long key, TPoolKey poolKey, TValue value, int size) + { + Key = key; + PoolKey = poolKey; + Value = value; + Size = size; + + _deleteDeps = new Queue(); + } + + public void UpdateCacheNode(LinkedListNode newNode) + { + Timestamp = PerformanceCounter.ElapsedMilliseconds; + + CacheNode = newNode; + } + + public void UpdatePoolNode(LinkedListNode newNode) + { + PoolNode = newNode; + } + + public void MarkAsOrphan() + { + Orphan = true; + } + + public void AddDependency(Action deleteDep) + { + _deleteDeps.Enqueue(deleteDep); + } + + public void DeleteAllDependencies() + { + while (_deleteDeps.TryDequeue(out Action deleteDep)) + { + deleteDep(); + } + } + } + + private Dictionary _cache; + + private Dictionary> _pool; + + private LinkedList _sortedCache; + + private LinkedListNode _poolTransferNode; + + private Action _deleteValueCallback; + + private Queue _deletionPending; + + private bool _locked; + + private int _maxSize; + private int _totalSize; + private int _minTimeForPoolTransfer; + + public OGLResourceCache( + Action deleteValueCallback, + int maxSize, + int minTimeForPoolTransfer = DefaultMinTimeForPoolTransfer) + { + _maxSize = maxSize; + + _deleteValueCallback = deleteValueCallback ?? throw new ArgumentNullException(nameof(deleteValueCallback)); + + _cache = new Dictionary(); + + _pool = new Dictionary>(); + + _sortedCache = new LinkedList(); + + _deletionPending = new Queue(); + } + + public void Lock() + { + //Locking ensure that no resources are deleted while + //the cache is locked, this prevent resources from + //being deleted or modified while in use. + _locked = true; + } + + public void Unlock() + { + _locked = false; + + while (_deletionPending.TryDequeue(out TValue Value)) + { + _deleteValueCallback(Value); + } + + ClearCacheIfNeeded(); + } + + public void AddOrUpdate(long key, TPoolKey poolKey, TValue value, int size) + { + if (!_locked) + { + ClearCacheIfNeeded(); + } + + CacheBucket newBucket = new CacheBucket(key, poolKey, value, size); + + newBucket.UpdateCacheNode(_sortedCache.AddLast(newBucket)); + + if (_cache.TryGetValue(key, out CacheBucket bucket)) + { + //A resource is considered orphan when it is no longer bound to + //a key, and has been replaced by a newer one. It may still be + //re-used. When the time expires, it will be deleted otherwise. + bucket.MarkAsOrphan(); + + //We need to delete all dependencies, to force them + //to use the updated handles, since we are replacing this + //resource on the cache with another one. + bucket.DeleteAllDependencies(); + } + + _totalSize += size; + + _cache[key] = newBucket; + } + + public void AddDependency(long key, OGLResourceCache soureCache, long sourceKey) + { + if (!_cache.TryGetValue(key, out CacheBucket bucket)) + { + return; + } + + bucket.AddDependency(() => soureCache.Delete(sourceKey)); + } + + public bool TryGetValue(long key, out TValue value) + { + if (_cache.TryGetValue(key, out CacheBucket bucket)) + { + AcquireResource(bucket); + + value = bucket.Value; + + return true; + } + + value = default(TValue); + + return false; + } + + public bool TryReuseValue(long key, TPoolKey poolKey, out TValue value) + { + if (_cache.TryGetValue(key, out CacheBucket bucket) && bucket.PoolKey.Equals(poolKey)) + { + //Value on key is already compatible, we don't need to do anything. + AcquireResource(bucket); + + value = bucket.Value; + + return true; + } + + if (_pool.TryGetValue(poolKey, out LinkedList queue)) + { + LinkedListNode node = queue.First; + + bucket = node.Value; + + Remove(bucket); + + AddOrUpdate(key, poolKey, bucket.Value, bucket.Size); + + value = bucket.Value; + + return true; + } + + value = default(TValue); + + return false; + } + + public bool TryGetSize(long key, out int size) + { + if (_cache.TryGetValue(key, out CacheBucket bucket)) + { + AcquireResource(bucket); + + size = bucket.Size; + + return true; + } + + size = 0; + + return false; + } + + public bool TryGetSizeAndValue(long key, out int size, out TValue value) + { + if (_cache.TryGetValue(key, out CacheBucket bucket)) + { + AcquireResource(bucket); + + size = bucket.Size; + value = bucket.Value; + + return true; + } + + size = 0; + value = default(TValue); + + return false; + } + + private void AcquireResource(CacheBucket bucket) + { + RemoveFromSortedCache(bucket.CacheNode); + + bucket.UpdateCacheNode(_sortedCache.AddLast(bucket.CacheNode.Value)); + + RemoveFromResourcePool(bucket); + } + + private void ClearCacheIfNeeded() + { + long timestamp = PerformanceCounter.ElapsedMilliseconds; + + for (int count = 0; count < MaxRemovalsPerRun; count++) + { + LinkedListNode node = _sortedCache.First; + + if (node == null) + { + break; + } + + CacheBucket bucket = node.Value; + + long timeDelta = timestamp - bucket.Timestamp; + + if (timeDelta <= MinTimeDelta && !UnderMemoryPressure()) + { + break; + } + + Delete(bucket); + } + + if (_poolTransferNode == null) + { + _poolTransferNode = _sortedCache.First; + } + + while (_poolTransferNode != null) + { + CacheBucket bucket = _poolTransferNode.Value; + + long timeDelta = timestamp - bucket.Timestamp; + + if (timeDelta <= _minTimeForPoolTransfer) + { + break; + } + + AddToResourcePool(bucket); + + _poolTransferNode = _poolTransferNode.Next; + } + } + + private bool UnderMemoryPressure() + { + return _totalSize >= _maxSize; + } + + private void Delete(long key) + { + if (!_cache.TryGetValue(key, out CacheBucket bucket)) + { + return; + } + + Delete(bucket); + } + + private void Delete(CacheBucket bucket) + { + Remove(bucket); + + if (_locked) + { + _deletionPending.Enqueue(bucket.Value); + } + else + { + _deleteValueCallback(bucket.Value); + } + } + + private void Remove(CacheBucket bucket) + { + if (!bucket.Orphan) + { + _cache.Remove(bucket.Key); + } + + RemoveFromSortedCache(bucket.CacheNode); + RemoveFromResourcePool(bucket); + + bucket.DeleteAllDependencies(); + + _totalSize -= bucket.Size; + } + + private void RemoveFromSortedCache(LinkedListNode node) + { + if (_poolTransferNode == node) + { + _poolTransferNode = node.Next; + } + + _sortedCache.Remove(node); + } + + private bool AddToResourcePool(CacheBucket bucket) + { + if (bucket.PoolNode == null) + { + if (!_pool.TryGetValue(bucket.PoolKey, out LinkedList queue)) + { + _pool.Add(bucket.PoolKey, queue = new LinkedList()); + } + + bucket.UpdatePoolNode(queue.AddLast(bucket)); + + return true; + } + + return false; + } + + private void RemoveFromResourcePool(CacheBucket bucket) + { + if (bucket.PoolNode != null) + { + LinkedList queue = bucket.PoolNode.List; + + queue.Remove(bucket.PoolNode); + + bucket.UpdatePoolNode(null); + + if (queue.Count == 0) + { + _pool.Remove(bucket.PoolKey); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs index b45a3a3a5a..296de9ee49 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs @@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL private Dictionary Programs; - public int CurrentProgramHandle { get; private set; } + private int CurrentProgramHandle; private OGLConstBuffer Buffer; @@ -103,20 +103,16 @@ namespace Ryujinx.Graphics.Gal.OpenGL public unsafe void SetExtraData(float FlipX, float FlipY, int Instance) { - BindProgram(); - EnsureExtraBlock(); GL.BindBuffer(BufferTarget.UniformBuffer, ExtraUboHandle); float* Data = stackalloc float[ExtraDataSize]; + Data[0] = FlipX; Data[1] = FlipY; Data[2] = BitConverter.Int32BitsToSingle(Instance); - //Invalidate buffer - GL.BufferData(BufferTarget.UniformBuffer, ExtraDataSize * sizeof(float), IntPtr.Zero, BufferUsageHint.StreamDraw); - GL.BufferSubData(BufferTarget.UniformBuffer, IntPtr.Zero, ExtraDataSize * sizeof(float), (IntPtr)Data); } @@ -172,7 +168,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL if (!Programs.TryGetValue(Current, out int Handle)) { - Handle = GL.CreateProgram(); + CurrentProgramHandle = Handle = GL.CreateProgram(); AttachIfNotNull(Handle, Current.Vertex); AttachIfNotNull(Handle, Current.TessControl); @@ -184,15 +180,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL CheckProgramLink(Handle); + GL.UseProgram(Handle); + BindUniformBlocks(Handle); BindTextureLocations(Handle); Programs.Add(Current, Handle); } + else if (CurrentProgramHandle != Handle) + { + CurrentProgramHandle = Handle; - GL.UseProgram(Handle); + GL.UseProgram(Handle); + } + } - CurrentProgramHandle = Handle; + private void AttachIfNotNull(int ProgramHandle, OGLShaderStage Stage) + { + if (Stage != null) + { + Stage.Compile(); + + GL.AttachShader(ProgramHandle, Stage.Handle); + } } private void EnsureExtraBlock() @@ -209,16 +219,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } - private void AttachIfNotNull(int ProgramHandle, OGLShaderStage Stage) - { - if (Stage != null) - { - Stage.Compile(); - - GL.AttachShader(ProgramHandle, Stage.Handle); - } - } - private void BindUniformBlocks(int ProgramHandle) { int ExtraBlockindex = GL.GetUniformBlockIndex(ProgramHandle, GlslDecl.ExtraUniformBlockName); @@ -274,8 +274,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL } } - GL.UseProgram(ProgramHandle); - BindTexturesIfNotNull(Current.Vertex); BindTexturesIfNotNull(Current.TessControl); BindTexturesIfNotNull(Current.TessEvaluation); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShaderProgram.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShaderProgram.cs index c87b0d4053..9b00813725 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLShaderProgram.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShaderProgram.cs @@ -1,6 +1,4 @@ -using OpenTK.Graphics.OpenGL; -using System; -using System.Collections.Generic; +using System; namespace Ryujinx.Graphics.Gal.OpenGL { @@ -11,76 +9,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL public OGLShaderStage TessEvaluation; public OGLShaderStage Geometry; public OGLShaderStage Fragment; - } - class OGLShaderStage : IDisposable - { - public int Handle { get; private set; } - - public bool IsCompiled { get; private set; } - - public GalShaderType Type { get; private set; } - - public string Code { get; private set; } - - public IEnumerable ConstBufferUsage { get; private set; } - public IEnumerable TextureUsage { get; private set; } - - public OGLShaderStage( - GalShaderType Type, - string Code, - IEnumerable ConstBufferUsage, - IEnumerable TextureUsage) + public override bool Equals(object obj) { - this.Type = Type; - this.Code = Code; - this.ConstBufferUsage = ConstBufferUsage; - this.TextureUsage = TextureUsage; - } - - public void Compile() - { - if (Handle == 0) + if (!(obj is OGLShaderProgram program)) { - Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type)); - - CompileAndCheck(Handle, Code); + return false; } + + return Vertex == program.Vertex && + TessControl == program.TessControl && + TessEvaluation == program.TessEvaluation && + Geometry == program.Geometry && + Fragment == program.Fragment; } - public void Dispose() + public override int GetHashCode() { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && Handle != 0) - { - GL.DeleteShader(Handle); - - Handle = 0; - } - } - - public static void CompileAndCheck(int Handle, string Code) - { - GL.ShaderSource(Handle, Code); - GL.CompileShader(Handle); - - CheckCompilation(Handle); - } - - private static void CheckCompilation(int Handle) - { - int Status = 0; - - GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status); - - if (Status == 0) - { - throw new ShaderException(GL.GetShaderInfoLog(Handle)); - } + return HashCode.Combine( + Vertex, + TessControl, + TessEvaluation, + Geometry, + Fragment); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShaderStage.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShaderStage.cs new file mode 100644 index 0000000000..667c62bc9b --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShaderStage.cs @@ -0,0 +1,77 @@ +using OpenTK.Graphics.OpenGL; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLShaderStage : IDisposable + { + public int Handle { get; private set; } + + public bool IsCompiled { get; private set; } + + public GalShaderType Type { get; private set; } + + public string Code { get; private set; } + + public IEnumerable ConstBufferUsage { get; private set; } + public IEnumerable TextureUsage { get; private set; } + + public OGLShaderStage( + GalShaderType Type, + string Code, + IEnumerable ConstBufferUsage, + IEnumerable TextureUsage) + { + this.Type = Type; + this.Code = Code; + this.ConstBufferUsage = ConstBufferUsage; + this.TextureUsage = TextureUsage; + } + + public void Compile() + { + if (Handle == 0) + { + Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type)); + + CompileAndCheck(Handle, Code); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool Disposing) + { + if (Disposing && Handle != 0) + { + GL.DeleteShader(Handle); + + Handle = 0; + } + } + + public static void CompileAndCheck(int Handle, string Code) + { + GL.ShaderSource(Handle, Code); + GL.CompileShader(Handle); + + CheckCompilation(Handle); + } + + private static void CheckCompilation(int Handle) + { + int Status = 0; + + GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status); + + if (Status == 0) + { + throw new ShaderException(GL.GetShaderInfoLog(Handle)); + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs index 411d33aab7..8d929da69e 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLStreamBuffer.cs @@ -11,30 +11,30 @@ namespace Ryujinx.Graphics.Gal.OpenGL protected BufferTarget Target { get; private set; } - public OGLStreamBuffer(BufferTarget Target, long Size) + public OGLStreamBuffer(BufferTarget target, int size) { - this.Target = Target; - this.Size = Size; + Target = target; + Size = size; Handle = GL.GenBuffer(); - GL.BindBuffer(Target, Handle); + GL.BindBuffer(target, Handle); - GL.BufferData(Target, (IntPtr)Size, IntPtr.Zero, BufferUsageHint.StreamDraw); + GL.BufferData(target, new IntPtr(size), IntPtr.Zero, BufferUsageHint.StreamDraw); } - public void SetData(long Size, IntPtr HostAddress) + public void SetData(IntPtr hostAddress, int size) { GL.BindBuffer(Target, Handle); - GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)Size, HostAddress); + GL.BufferSubData(Target, IntPtr.Zero, new IntPtr(size), hostAddress); } - public void SetData(byte[] Data) + public void SetData(byte[] buffer) { GL.BindBuffer(Target, Handle); - GL.BufferSubData(Target, IntPtr.Zero, (IntPtr)Data.Length, Data); + GL.BufferSubData(Target, IntPtr.Zero, new IntPtr(buffer.Length), buffer); } public void Dispose() @@ -42,9 +42,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL Dispose(true); } - protected virtual void Dispose(bool Disposing) + protected virtual void Dispose(bool disposing) { - if (Disposing && Handle != 0) + if (disposing && Handle != 0) { GL.DeleteBuffer(Handle); diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index fb8a6a72da..2a8914d770 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -6,14 +6,49 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLTexture : IGalTexture { + private struct ImageKey + { + public int Width { get; private set; } + public int Height { get; private set; } - private OGLCachedResource TextureCache; + public GalImageFormat Format { get; private set; } + + public ImageKey(GalImage image) + { + Width = image.Width; + Height = image.Height; + Format = image.Format; + } + + public override bool Equals(object obj) + { + if (!(obj is ImageKey imgKey)) + { + return false; + } + + return Width == imgKey.Width && + Height == imgKey.Height && + Format == imgKey.Format; + } + + public override int GetHashCode() + { + return HashCode.Combine(Width, Height, Format); + } + } + + private OGLResourceCache TextureCache; + + private OGLResourceCache PboCache; public EventHandler TextureDeleted { get; set; } public OGLTexture() { - TextureCache = new OGLCachedResource(DeleteTexture, OGLResourceLimits.TextureLimit); + TextureCache = new OGLResourceCache(DeleteTexture, OGLResourceLimits.TextureLimit); + + PboCache = new OGLResourceCache(GL.DeleteBuffer, OGLResourceLimits.PixelBufferLimit, 0); } public void LockCache() @@ -35,15 +70,32 @@ namespace Ryujinx.Graphics.Gal.OpenGL public void Create(long Key, int Size, GalImage Image) { - int Handle = GL.GenTexture(); + CreateFromPboOrEmpty(Key, Size, Image, IsEmpty: true); + } - GL.BindTexture(TextureTarget.Texture2D, Handle); + private void CreateFromPboOrEmpty(long Key, int Size, GalImage Image, bool IsEmpty = false) + { + ImageKey imageKey = new ImageKey(Image); + + if (TextureCache.TryReuseValue(Key, imageKey, out ImageHandler CachedImage)) + { + if (IsEmpty) + { + return; + } + } + else + { + CachedImage = new ImageHandler(GL.GenTexture(), Image); + + TextureCache.AddOrUpdate(Key, imageKey, CachedImage, Size); + } + + GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle); const int Level = 0; //TODO: Support mipmap textures. const int Border = 0; - TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Size); - if (ImageUtils.IsCompressed(Image.Format)) { throw new InvalidOperationException("Surfaces with compressed formats are not supported!"); @@ -74,7 +126,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL const int Level = 0; //TODO: Support mipmap textures. const int Border = 0; - TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length); + ImageKey imageKey = new ImageKey(Image); + + TextureCache.AddOrUpdate(Key, imageKey, new ImageHandler(Handle, Image), Data.Length); if (ImageUtils.IsCompressed(Image.Format) && !IsAstc(Image.Format)) { diff --git a/Ryujinx.Graphics/QuadHelper.cs b/Ryujinx.Graphics/Graphics3d/IbHelper.cs similarity index 59% rename from Ryujinx.Graphics/QuadHelper.cs rename to Ryujinx.Graphics/Graphics3d/IbHelper.cs index 0dfffce0bc..362b19c33d 100644 --- a/Ryujinx.Graphics/QuadHelper.cs +++ b/Ryujinx.Graphics/Graphics3d/IbHelper.cs @@ -1,8 +1,10 @@ +using Ryujinx.Graphics.Gal; using System; +using System.Buffers.Binary; -namespace Ryujinx.Graphics +namespace Ryujinx.Graphics.Graphics3d { - static class QuadHelper + static class IbHelper { public static int ConvertIbSizeQuadsToTris(int Size) { @@ -77,5 +79,61 @@ namespace Ryujinx.Graphics return Output; } + + public static int GetVertexCountFromIb16(byte[] data) + { + if (data.Length == 0) + { + return 0; + } + + ushort max = 0; + + for (int index = 0; index < data.Length; index += 2) + { + ushort value = BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(index, 2)); + + if (max < value) + { + max = value; + } + } + + return max + 1; + } + + public static long GetVertexCountFromIb32(byte[] data) + { + if (data.Length == 0) + { + return 0; + } + + uint max = 0; + + for (int index = 0; index < data.Length; index += 4) + { + uint value = BinaryPrimitives.ReadUInt32LittleEndian(data.AsSpan(index, 4)); + + if (max < value) + { + max = value; + } + } + + return max + 1; + } + + public static long GetIbMaxVertexCount(GalIndexFormat format) + { + switch (format) + { + case GalIndexFormat.Byte: return 1L << 8; + case GalIndexFormat.Int16: return 1L << 16; + case GalIndexFormat.Int32: return 1L << 32; + } + + throw new ArgumentException(nameof(format)); + } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs index 424754aa69..970e93d12e 100644 --- a/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs +++ b/Ryujinx.Graphics/Graphics3d/NvGpuEngine3d.cs @@ -64,6 +64,53 @@ namespace Ryujinx.Graphics.Graphics3d AddMethod(0x8e4, 16, 1, CbData); AddMethod(0x904, 5, 8, CbBind); + AddMethod((int)NvGpuEngine3dReg.DepthTestEnable, 1, 1, SetDepth); + AddMethod((int)NvGpuEngine3dReg.DepthWriteEnable, 1, 1, SetDepth); + AddMethod((int)NvGpuEngine3dReg.DepthTestFunction, 1, 1, SetDepth); + AddMethod((int)NvGpuEngine3dReg.DepthRangeNNear, 1, 1, SetDepth); + AddMethod((int)NvGpuEngine3dReg.DepthRangeNFar, 1, 1, SetDepth); + + AddMethod((int)NvGpuEngine3dReg.StencilEnable, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackFuncFunc, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackFuncRef, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackFuncMask, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackOpFail, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackOpZFail, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackOpZPass, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilBackMask, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontFuncFunc, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontFuncRef, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontFuncMask, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontOpFail, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontOpZFail, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontOpZPass, 1, 1, SetStencil); + AddMethod((int)NvGpuEngine3dReg.StencilFrontMask, 1, 1, SetStencil); + + AddMethod((int)NvGpuEngine3dReg.BlendIndependent, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNEnable, 8, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNSeparateAlpha, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNEquationRgb, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNFuncSrcRgb, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNFuncDstRgb, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNEquationAlpha, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNFuncSrcAlpha, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.IBlendNFuncDstAlpha, 8, 8, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendSeparateAlpha, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendEquationRgb, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendFuncSrcRgb, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendFuncDstRgb, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendEquationAlpha, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendFuncSrcAlpha, 1, 1, SetBlend); + AddMethod((int)NvGpuEngine3dReg.BlendFuncDstAlpha, 1, 1, SetBlend); + + AddMethod((int)NvGpuEngine3dReg.ColorMaskCommon, 1, 1, SetColorMask); + AddMethod((int)NvGpuEngine3dReg.ColorMaskN, 8, 1, SetColorMask); + + AddMethod((int)NvGpuEngine3dReg.PrimRestartEnable, 1, 1, SetPrimRestart); + AddMethod((int)NvGpuEngine3dReg.PrimRestartIndex, 1, 1, SetPrimRestart); + + AddMethod((int)NvGpuEngine3dReg.FrameBufferSrgb, 1, 1, SetFramebufferSrgb); + ConstBuffers = new ConstBuffer[6][]; for (int Index = 0; Index < ConstBuffers.Length; Index++) @@ -726,15 +773,17 @@ namespace Ryujinx.Graphics.Graphics3d long Key = Vmm.GetPhysicalAddress(Cb.Position); - if (Gpu.ResourceManager.MemoryRegionModified(Vmm, Key, Cb.Size, NvGpuBufferType.ConstBuffer)) + bool CbCached = Gpu.Renderer.Buffer.IsCached(Key, Cb.Size); + + if (Gpu.ResourceManager.MemoryRegionModified(Vmm, Key, Cb.Size, NvGpuBufferType.ConstBuffer) || !CbCached) { if (Vmm.TryGetHostAddress(Cb.Position, Cb.Size, out IntPtr CbPtr)) { - Gpu.Renderer.Buffer.SetData(Key, Cb.Size, CbPtr); + Gpu.Renderer.Buffer.Create(Key, CbPtr, Cb.Size); } else { - Gpu.Renderer.Buffer.SetData(Key, Vmm.ReadBytes(Cb.Position, Cb.Size)); + Gpu.Renderer.Buffer.Create(Key, Vmm.ReadBytes(Cb.Position, Cb.Size)); } } @@ -753,7 +802,9 @@ namespace Ryujinx.Graphics.Graphics3d long IboKey = Vmm.GetPhysicalAddress(IbPosition); int IndexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat); + int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst); int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount); + int VertexBase = ReadRegister(NvGpuEngine3dReg.VertexArrayElemBase); int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); @@ -764,30 +815,61 @@ namespace Ryujinx.Graphics.Graphics3d if (IndexEntrySize > 4) { - throw new InvalidOperationException("Invalid index entry size \"" + IndexEntrySize + "\"!"); + throw new InvalidOperationException($"Invalid index entry size \"{IndexEntrySize}\"."); } + long IbVtxCount = 0; + if (IndexCount != 0) { - int IbSize = IndexCount * IndexEntrySize; + int IbSize = (IndexFirst + IndexCount) * IndexEntrySize; - bool IboCached = Gpu.Renderer.Rasterizer.IsIboCached(IboKey, (uint)IbSize); + int HostIbSize = IbSize; bool UsesLegacyQuads = PrimType == GalPrimitiveType.Quads || PrimType == GalPrimitiveType.QuadStrip; - if (!IboCached || Gpu.ResourceManager.MemoryRegionModified(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index)) + if (UsesLegacyQuads) { + if (PrimType == GalPrimitiveType.Quads) + { + HostIbSize = IbHelper.ConvertIbSizeQuadsToTris(HostIbSize); + } + else /* if (PrimType == GalPrimitiveType.QuadStrip) */ + { + HostIbSize = IbHelper.ConvertIbSizeQuadStripToTris(HostIbSize); + } + } + + bool IboCached = Gpu.Renderer.Rasterizer.IsIboCached(IboKey, HostIbSize, out IbVtxCount); + + if (Gpu.ResourceManager.MemoryRegionModified(Vmm, IboKey, (uint)IbSize, NvGpuBufferType.Index) || !IboCached) + { + IbVtxCount = IbHelper.GetIbMaxVertexCount(IndexFormat); + if (!UsesLegacyQuads) { - if (Vmm.TryGetHostAddress(IbPosition, IbSize, out IntPtr IbPtr)) + bool ShallGetVertexCount = IndexFormat != GalIndexFormat.Byte; + + if (!ShallGetVertexCount && Vmm.TryGetHostAddress(IbPosition, IbSize, out IntPtr IbPtr)) { - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, IbPtr); + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbPtr, IbSize, IbVtxCount); } else { - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, Vmm.ReadBytes(IbPosition, IbSize)); + byte[] Data = Vmm.ReadBytes(IbPosition, IbSize); + + if (IndexFormat == GalIndexFormat.Int16) + { + IbVtxCount = IbHelper.GetVertexCountFromIb16(Data); + } + else if (IndexFormat == GalIndexFormat.Int32) + { + IbVtxCount = IbHelper.GetVertexCountFromIb32(Data); + } + + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, Data, IbVtxCount); } } else @@ -796,14 +878,14 @@ namespace Ryujinx.Graphics.Graphics3d if (PrimType == GalPrimitiveType.Quads) { - Buffer = QuadHelper.ConvertIbQuadsToTris(Buffer, IndexEntrySize, IndexCount); + Buffer = IbHelper.ConvertIbQuadsToTris(Buffer, IndexEntrySize, IndexCount); } else /* if (PrimType == GalPrimitiveType.QuadStrip) */ { - Buffer = QuadHelper.ConvertIbQuadStripToTris(Buffer, IndexEntrySize, IndexCount); + Buffer = IbHelper.ConvertIbQuadStripToTris(Buffer, IndexEntrySize, IndexCount); } - Gpu.Renderer.Rasterizer.CreateIbo(IboKey, IbSize, Buffer); + Gpu.Renderer.Rasterizer.CreateIbo(IboKey, Buffer, IbVtxCount); } } @@ -815,53 +897,56 @@ namespace Ryujinx.Graphics.Graphics3d { if (PrimType == GalPrimitiveType.Quads) { - Gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertIbSizeQuadsToTris(IbSize), IndexFormat); + Gpu.Renderer.Rasterizer.SetIndexArray(IbHelper.ConvertIbSizeQuadsToTris(IbSize), IndexFormat); } else /* if (PrimType == GalPrimitiveType.QuadStrip) */ { - Gpu.Renderer.Rasterizer.SetIndexArray(QuadHelper.ConvertIbSizeQuadStripToTris(IbSize), IndexFormat); + Gpu.Renderer.Rasterizer.SetIndexArray(IbHelper.ConvertIbSizeQuadStripToTris(IbSize), IndexFormat); } } } - List[] Attribs = new List[32]; - - for (int Attr = 0; Attr < 16; Attr++) - { - int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr); - - int ArrayIndex = Packed & 0x1f; - - if (Attribs[ArrayIndex] == null) - { - Attribs[ArrayIndex] = new List(); - } - - long VbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + ArrayIndex * 4); - - bool IsConst = ((Packed >> 6) & 1) != 0; - - int Offset = (Packed >> 7) & 0x3fff; - - GalVertexAttribSize Size = (GalVertexAttribSize)((Packed >> 21) & 0x3f); - GalVertexAttribType Type = (GalVertexAttribType)((Packed >> 27) & 0x7); - - bool IsRgba = ((Packed >> 31) & 1) != 0; - - //Note: 16 is the maximum size of an attribute, - //having a component size of 32-bits with 4 elements (a vec4). - byte[] Data = Vmm.ReadBytes(VbPosition + Offset, 16); - - Attribs[ArrayIndex].Add(new GalVertexAttrib(Attr, IsConst, Offset, Data, Size, Type, IsRgba)); - } + //Get vertex arrays and attributes count from attribute registers. + int AttribsCount = 0; + int ArraysCount = 0; for (int Index = 0; Index < 32; Index++) { - if (Attribs[Index] == null) + int Packed = Registers[(int)NvGpuEngine3dReg.VertexAttribNFormat + Index]; + + //The size is a 3 bits field, starting at bit 27. + //If size is 0, then the attribute is unused, skip it. + if ((Packed & (7 << 27)) == 0) { continue; } + if (AttribsCount < Index) + { + AttribsCount = Index; + } + + int ArrayIndex = Packed & 0x1f; + + if (ArraysCount < ArrayIndex) + { + ArraysCount = ArrayIndex; + } + } + + //Those are actually the last valid indices, so we need + //to add 1 to get the count. + AttribsCount++; + ArraysCount++; + + //Upload vertex buffers, build vertex array info table. + GalVertexAttribArray[] Arrays = new GalVertexAttribArray[ArraysCount]; + + int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst); + int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount); + + for (int Index = 0; Index < ArraysCount; Index++) + { int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4); bool Enable = (Control & 0x1000) != 0; @@ -895,19 +980,71 @@ namespace Ryujinx.Graphics.Graphics3d long VbSize = (VbEndPos - VbPosition) + 1; - bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, VbSize); + long MaxVbSize = (IndexCount != 0 + ? VertexBase + IbVtxCount + : VertexFirst + VertexCount) * Stride; - if (!VboCached || Gpu.ResourceManager.MemoryRegionModified(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex)) + if (MaxVbSize < VbSize) + { + VbSize = MaxVbSize; + } + + bool VboCached = Gpu.Renderer.Rasterizer.IsVboCached(VboKey, (int)VbSize); + + if (Gpu.ResourceManager.MemoryRegionModified(Vmm, VboKey, VbSize, NvGpuBufferType.Vertex) || !VboCached) { if (Vmm.TryGetHostAddress(VbPosition, VbSize, out IntPtr VbPtr)) { - Gpu.Renderer.Rasterizer.CreateVbo(VboKey, (int)VbSize, VbPtr); + Gpu.Renderer.Rasterizer.CreateVbo(VboKey, VbPtr, (int)VbSize); } else { Gpu.Renderer.Rasterizer.CreateVbo(VboKey, Vmm.ReadBytes(VbPosition, VbSize)); } } + + Arrays[Index] = new GalVertexAttribArray(VboKey, Stride, Instanced ? VertexDivisor : 0); + } + + //Set vertex attributes. + ReadOnlySpan RawAttribs = new ReadOnlySpan(Registers, (int)NvGpuEngine3dReg.VertexAttribNFormat, AttribsCount); + + if (!Gpu.Renderer.Rasterizer.TryBindVao(RawAttribs, Arrays)) + { + GalVertexAttrib[] Attributes = new GalVertexAttrib[AttribsCount]; + + for (int Index = 0; Index < AttribsCount; Index++) + { + int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Index); + + int ArrayIndex = Packed & 0x1f; + + long VbPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + ArrayIndex * 4); + + bool IsConst = ((Packed >> 6) & 1) != 0; + + int Offset = (Packed >> 7) & 0x3fff; + + //Note: 16 is the maximum size of an attribute, + //having a component size of 32-bits with 4 elements (a vec4). + byte[] Data = Vmm.ReadBytes(VbPosition + Offset, 16); + + GalVertexAttribSize Size = (GalVertexAttribSize)((Packed >> 21) & 0x3f); + GalVertexAttribType Type = (GalVertexAttribType)((Packed >> 27) & 0x7); + + bool IsRgba = ((Packed >> 31) & 1) != 0; + + Attributes[Index] = new GalVertexAttrib( + IsConst, + ArrayIndex, + Offset, + Data, + Size, + Type, + IsRgba); + } + + Gpu.Renderer.Rasterizer.CreateVao(RawAttribs, Attributes, Arrays); } } @@ -974,11 +1111,11 @@ namespace Ryujinx.Graphics.Graphics3d //quad (First % 4 != 0 for Quads) then it will not work properly. if (PrimType == GalPrimitiveType.Quads) { - IndexFirst = QuadHelper.ConvertIbSizeQuadsToTris(IndexFirst); + IndexFirst = IbHelper.ConvertIbSizeQuadsToTris(IndexFirst); } else /* if (PrimType == GalPrimitiveType.QuadStrip) */ { - IndexFirst = QuadHelper.ConvertIbSizeQuadStripToTris(IndexFirst); + IndexFirst = IbHelper.ConvertIbSizeQuadStripToTris(IndexFirst); } } @@ -1063,23 +1200,11 @@ namespace Ryujinx.Graphics.Graphics3d long Position = MakeInt64From2xInt32(NvGpuEngine3dReg.ConstBufferAddress); - long CbKey = Vmm.GetPhysicalAddress(Position); - int Size = ReadRegister(NvGpuEngine3dReg.ConstBufferSize); - if (!Gpu.Renderer.Buffer.IsCached(CbKey, Size)) - { - Gpu.Renderer.Buffer.Create(CbKey, Size); - } - - ConstBuffer Cb = ConstBuffers[Stage][Index]; - - if (Cb.Position != Position || Cb.Enabled != Enabled || Cb.Size != Size) - { - ConstBuffers[Stage][Index].Position = Position; - ConstBuffers[Stage][Index].Enabled = Enabled; - ConstBuffers[Stage][Index].Size = Size; - } + ConstBuffers[Stage][Index].Enabled = Enabled; + ConstBuffers[Stage][Index].Position = Position; + ConstBuffers[Stage][Index].Size = Size; } private float GetFlipSign(NvGpuEngine3dReg Reg)