Cache changes

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

View file

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

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

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,33 +126,53 @@ 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();
}
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<int> 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<int> 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<GalVertexAttribSize, VertexAttribPointerType> 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;
}
}
}

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

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