Cache changes

This commit is contained in:
gdkchan 2018-12-11 10:10:42 +01:00 committed by Roderick Sieben
commit b0d953d6bb
18 changed files with 1414 additions and 518 deletions

View file

@ -2,8 +2,8 @@ namespace Ryujinx.Graphics.Gal
{
public struct GalVertexAttrib
{
public int Index { 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; }
@ -13,17 +13,17 @@ 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.ArrayIndex = ArrayIndex;
this.Offset = Offset;
this.Size = Size;
this.Type = Type;

View file

@ -0,0 +1,43 @@
using System;
namespace Ryujinx.Graphics.Gal
{
public struct GalVertexAttribArray : IEquatable<GalVertexAttribArray>
{
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);
}
}
}

View file

@ -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);
}
}

View file

@ -17,15 +17,21 @@ namespace Ryujinx.Graphics.Gal
float Depth,
int Stencil);
bool IsVboCached(long Key, long DataSize);
bool TryBindVao(ReadOnlySpan<int> rawAttributes, GalVertexAttribArray[] arrays);
bool IsIboCached(long Key, long DataSize);
void CreateVao(
ReadOnlySpan<int> 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);

View file

@ -37,7 +37,5 @@ namespace Ryujinx.Graphics.Gal
int DstY0,
int DstX1,
int DstY1);
void Reinterpret(long Key, GalImage NewImage);
}
}

View file

@ -11,6 +11,8 @@ namespace Ryujinx.Graphics.Gal
IEnumerable<ShaderDeclInfo> GetConstBufferUsage(long Key);
IEnumerable<ShaderDeclInfo> GetTextureUsage(long Key);
void SetExtraData(float FlipX, float FlipY, int Instance);
void Bind(long Key);
void Unbind(GalShaderType Type);

View file

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

View file

@ -1,16 +1,20 @@
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics.OpenGL;
using System;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLConstBuffer : IGalConstBuffer
{
private OGLCachedResource<OGLStreamBuffer> Cache;
private OGLResourceCache<int, OGLStreamBuffer> Cache;
public OGLConstBuffer()
{
Cache = new OGLCachedResource<OGLStreamBuffer>(DeleteBuffer, OGLResourceLimits.ConstBufferLimit);
Cache = new OGLResourceCache<int, OGLStreamBuffer>(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))
{
Buffer.SetData(Size, HostAddress);
}
GetBuffer(key, buffer.Length).SetData(buffer);
}
public void SetData(long Key, byte[] Data)
public bool TryGetUbo(long key, out int uboHandle)
{
if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer))
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;
}
}
}

View file

@ -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<GalVertexAttribSize, int> AttribElements =
new Dictionary<GalVertexAttribSize, int>()
{
{ 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<GalVertexAttribSize, VertexAttribPointerType> FloatAttribTypes =
new Dictionary<GalVertexAttribSize, VertexAttribPointerType>()
{
{ 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<GalVertexAttribSize, VertexAttribPointerType> SignedAttribTypes =
new Dictionary<GalVertexAttribSize, VertexAttribPointerType>()
{
{ 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<GalVertexAttribSize, VertexAttribPointerType> UnsignedAttribTypes =
new Dictionary<GalVertexAttribSize, VertexAttribPointerType>()
{
{ 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<int> VboCache;
private OGLCachedResource<int> 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<int, CachedVao> VaoCache;
private OGLResourceCache<int, OGLStreamBuffer> VboCache;
private class CachedIbo
{
public OGLStreamBuffer Buffer { get; }
public long VertexCount { get; set; }
public CachedIbo(OGLStreamBuffer buffer)
{
Buffer = buffer;
}
}
private OGLResourceCache<int, CachedIbo> IboCache;
private struct IbInfo
{
@ -27,20 +126,40 @@ namespace Ryujinx.Graphics.Gal.OpenGL
{
VertexBuffers = new int[32];
VboCache = new OGLCachedResource<int>(GL.DeleteBuffer, MaxVertexBufferCacheSize);
IboCache = new OGLCachedResource<int>(GL.DeleteBuffer, MaxIndexBufferCacheSize);
VaoCache = new OGLResourceCache<int, CachedVao>(DeleteVao, OGLResourceLimits.VertexArrayLimit);
VboCache = new OGLResourceCache<int, OGLStreamBuffer>(DeleteBuffer, OGLResourceLimits.VertexBufferLimit);
IboCache = new OGLResourceCache<int, CachedIbo>(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();
}
@ -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;
}
public void CreateVbo(long Key, int DataSize, IntPtr HostAddress)
{
int Handle = GL.GenBuffer();
vertexCount = 0;
VboCache.AddOrUpdate(Key, Handle, DataSize);
IntPtr Length = new IntPtr(DataSize);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferData(BufferTarget.ArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
return false;
}
public void CreateVbo(long Key, byte[] Data)
public bool TryBindVao(ReadOnlySpan<int> rawAttributes, GalVertexAttribArray[] arrays)
{
int Handle = GL.GenBuffer();
long hash = CalculateHash(arrays);
VboCache.AddOrUpdate(Key, Handle, Data.Length);
IntPtr Length = new IntPtr(Data.Length);
GL.BindBuffer(BufferTarget.ArrayBuffer, Handle);
GL.BufferData(BufferTarget.ArrayBuffer, Length, Data, BufferUsageHint.StreamDraw);
if (!VaoCache.TryGetValue(hash, out CachedVao vao))
{
return false;
}
public void CreateIbo(long Key, int DataSize, IntPtr HostAddress)
if (rawAttributes.Length != vao.Attributes.Length)
{
int Handle = GL.GenBuffer();
IboCache.AddOrUpdate(Key, Handle, (uint)DataSize);
IntPtr Length = new IntPtr(DataSize);
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
GL.BufferData(BufferTarget.ElementArrayBuffer, Length, HostAddress, BufferUsageHint.StreamDraw);
return false;
}
public void CreateIbo(long Key, int DataSize, byte[] Buffer)
if (arrays.Length != vao.Arrays.Length)
{
int Handle = GL.GenBuffer();
return false;
}
IboCache.AddOrUpdate(Key, Handle, DataSize);
for (int index = 0; index < rawAttributes.Length; index++)
{
if (rawAttributes[index] != vao.Attributes[index])
{
return false;
}
}
IntPtr Length = new IntPtr(Buffer.Length);
for (int index = 0; index < arrays.Length; index++)
{
if (!arrays[index].Equals(vao.Arrays[index]))
{
return false;
}
}
GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle);
GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
GL.BindVertexArray(vao.Handle);
return true;
}
public void CreateVao(
ReadOnlySpan<int> rawAttributes,
GalVertexAttrib[] attributes,
GalVertexAttribArray[] arrays)
{
CachedVao vao = new CachedVao(rawAttributes.ToArray(), arrays);
long hash = CalculateHash(arrays);
VaoCache.AddOrUpdate(hash, 1, vao, 1);
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);
}
}
}
private long CalculateHash(GalVertexAttribArray[] arrays)
{
if (arrays.Length == 1)
{
return arrays[0].VboKey;
}
long hash = 17;
for (int index = 0; index < arrays.Length; index++)
{
hash = hash * 23 + arrays[index].VboKey;
}
return hash;
}
private static VertexAttribPointerType GetType(Dictionary<GalVertexAttribSize, VertexAttribPointerType> Dict, GalVertexAttrib Attrib)
{
if (!Dict.TryGetValue(Attrib.Size, out VertexAttribPointerType Type))
{
ThrowUnsupportedAttrib(Attrib);
}
return Type;
}
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);
}
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;
}
}
}

View file

@ -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)

View file

@ -0,0 +1,393 @@
using Ryujinx.Common;
using System;
using System.Collections.Generic;
namespace Ryujinx.Graphics.Gal.OpenGL
{
class OGLResourceCache<TPoolKey, TValue>
{
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<CacheBucket> CacheNode { get; private set; }
public LinkedListNode<CacheBucket> PoolNode { get; private set; }
private Queue<Action> _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<Action>();
}
public void UpdateCacheNode(LinkedListNode<CacheBucket> newNode)
{
Timestamp = PerformanceCounter.ElapsedMilliseconds;
CacheNode = newNode;
}
public void UpdatePoolNode(LinkedListNode<CacheBucket> 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<long, CacheBucket> _cache;
private Dictionary<TPoolKey, LinkedList<CacheBucket>> _pool;
private LinkedList<CacheBucket> _sortedCache;
private LinkedListNode<CacheBucket> _poolTransferNode;
private Action<TValue> _deleteValueCallback;
private Queue<TValue> _deletionPending;
private bool _locked;
private int _maxSize;
private int _totalSize;
private int _minTimeForPoolTransfer;
public OGLResourceCache(
Action<TValue> deleteValueCallback,
int maxSize,
int minTimeForPoolTransfer = DefaultMinTimeForPoolTransfer)
{
_maxSize = maxSize;
_deleteValueCallback = deleteValueCallback ?? throw new ArgumentNullException(nameof(deleteValueCallback));
_cache = new Dictionary<long, CacheBucket>();
_pool = new Dictionary<TPoolKey, LinkedList<CacheBucket>>();
_sortedCache = new LinkedList<CacheBucket>();
_deletionPending = new Queue<TValue>();
}
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<T, U>(long key, OGLResourceCache<T, U> 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<CacheBucket> queue))
{
LinkedListNode<CacheBucket> 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<CacheBucket> 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<CacheBucket> 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<CacheBucket> queue))
{
_pool.Add(bucket.PoolKey, queue = new LinkedList<CacheBucket>());
}
bucket.UpdatePoolNode(queue.AddLast(bucket));
return true;
}
return false;
}
private void RemoveFromResourcePool(CacheBucket bucket)
{
if (bucket.PoolNode != null)
{
LinkedList<CacheBucket> queue = bucket.PoolNode.List;
queue.Remove(bucket.PoolNode);
bucket.UpdatePoolNode(null);
if (queue.Count == 0)
{
_pool.Remove(bucket.PoolKey);
}
}
}
}
}

View file

@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
private Dictionary<OGLShaderProgram, int> 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);
}
}
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);

View file

@ -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 override bool Equals(object obj)
{
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<ShaderDeclInfo> ConstBufferUsage { get; private set; }
public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; }
public OGLShaderStage(
GalShaderType Type,
string Code,
IEnumerable<ShaderDeclInfo> ConstBufferUsage,
IEnumerable<ShaderDeclInfo> TextureUsage)
if (!(obj is OGLShaderProgram program))
{
this.Type = Type;
this.Code = Code;
this.ConstBufferUsage = ConstBufferUsage;
this.TextureUsage = TextureUsage;
return false;
}
public void Compile()
return Vertex == program.Vertex &&
TessControl == program.TessControl &&
TessEvaluation == program.TessEvaluation &&
Geometry == program.Geometry &&
Fragment == program.Fragment;
}
public override int GetHashCode()
{
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));
}
return HashCode.Combine(
Vertex,
TessControl,
TessEvaluation,
Geometry,
Fragment);
}
}
}

View file

@ -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<ShaderDeclInfo> ConstBufferUsage { get; private set; }
public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; }
public OGLShaderStage(
GalShaderType Type,
string Code,
IEnumerable<ShaderDeclInfo> ConstBufferUsage,
IEnumerable<ShaderDeclInfo> 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));
}
}
}
}

View file

@ -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);

View file

@ -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<ImageHandler> 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<ImageKey, ImageHandler> TextureCache;
private OGLResourceCache<int, int> PboCache;
public EventHandler<int> TextureDeleted { get; set; }
public OGLTexture()
{
TextureCache = new OGLCachedResource<ImageHandler>(DeleteTexture, OGLResourceLimits.TextureLimit);
TextureCache = new OGLResourceCache<ImageKey, ImageHandler>(DeleteTexture, OGLResourceLimits.TextureLimit);
PboCache = new OGLResourceCache<int, int>(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))
{

View file

@ -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));
}
}
}

View file

@ -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<GalVertexAttrib>[] Attribs = new List<GalVertexAttrib>[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<GalVertexAttrib>();
}
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<int> RawAttribs = new ReadOnlySpan<int>(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,24 +1200,12 @@ 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].Position = Position;
ConstBuffers[Stage][Index].Size = Size;
}
}
private float GetFlipSign(NvGpuEngine3dReg Reg)
{