diff --git a/ChocolArm64/Memory/AMemory.cs b/ChocolArm64/Memory/AMemory.cs index e7d46565f..7e9a358ab 100644 --- a/ChocolArm64/Memory/AMemory.cs +++ b/ChocolArm64/Memory/AMemory.cs @@ -54,7 +54,14 @@ namespace ChocolArm64.Memory ExAddrs = new HashSet(); - Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize + AMemoryMgr.PageSize); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + Ram = AMemoryWin32.Allocate((IntPtr)AMemoryMgr.RamSize + AMemoryMgr.PageSize); + } + else + { + Ram = Marshal.AllocHGlobal((IntPtr)AMemoryMgr.RamSize + AMemoryMgr.PageSize); + } RamPtr = (byte*)Ram; } @@ -141,6 +148,51 @@ namespace ChocolArm64.Memory } } + public long GetHostPageSize() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return AMemoryMgr.PageSize; + } + + IntPtr MemAddress = new IntPtr(RamPtr); + IntPtr MemSize = new IntPtr(AMemoryMgr.RamSize); + + long PageSize = AMemoryWin32.IsRegionModified(MemAddress, MemSize, Reset: false); + + if (PageSize < 1) + { + throw new InvalidOperationException(); + } + + return PageSize; + } + + public bool IsRegionModified(long Position, long Size) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return true; + } + + long EndPos = Position + Size; + + if ((ulong)EndPos < (ulong)Position) + { + return false; + } + + if ((ulong)EndPos > AMemoryMgr.RamSize) + { + return false; + } + + IntPtr MemAddress = new IntPtr(RamPtr + Position); + IntPtr MemSize = new IntPtr(Size); + + return AMemoryWin32.IsRegionModified(MemAddress, MemSize, Reset: true) != 0; + } + public sbyte ReadSByte(long Position) { return (sbyte)ReadByte(Position); @@ -640,7 +692,14 @@ namespace ChocolArm64.Memory { if (Ram != IntPtr.Zero) { - Marshal.FreeHGlobal(Ram); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + AMemoryWin32.Free(Ram); + } + else + { + Marshal.FreeHGlobal(Ram); + } Ram = IntPtr.Zero; } diff --git a/ChocolArm64/Memory/AMemoryWin32.cs b/ChocolArm64/Memory/AMemoryWin32.cs new file mode 100644 index 000000000..d097dc871 --- /dev/null +++ b/ChocolArm64/Memory/AMemoryWin32.cs @@ -0,0 +1,73 @@ +using System; +using System.Runtime.InteropServices; + +namespace ChocolArm64.Memory +{ + static class AMemoryWin32 + { + private const int MEM_COMMIT = 0x00001000; + private const int MEM_RESERVE = 0x00002000; + private const int MEM_WRITE_WATCH = 0x00200000; + + private const int PAGE_READWRITE = 0x04; + + private const int MEM_RELEASE = 0x8000; + + private const int WRITE_WATCH_FLAG_RESET = 1; + + [DllImport("kernel32.dll")] + private static extern IntPtr VirtualAlloc(IntPtr lpAddress, IntPtr dwSize, int flAllocationType, int flProtect); + + [DllImport("kernel32.dll")] + private static extern bool VirtualFree(IntPtr lpAddress, IntPtr dwSize, int dwFreeType); + + [DllImport("kernel32.dll")] + private unsafe static extern int GetWriteWatch( + int dwFlags, + IntPtr lpBaseAddress, + IntPtr dwRegionSize, + IntPtr[] lpAddresses, + long* lpdwCount, + long* lpdwGranularity); + + public static IntPtr Allocate(IntPtr Size) + { + const int Flags = MEM_COMMIT | MEM_RESERVE | MEM_WRITE_WATCH; + + IntPtr Address = VirtualAlloc(IntPtr.Zero, Size, Flags, PAGE_READWRITE); + + if (Address == IntPtr.Zero) + { + throw new InvalidOperationException(); + } + + return Address; + } + + public static void Free(IntPtr Address) + { + VirtualFree(Address, IntPtr.Zero, MEM_RELEASE); + } + + public unsafe static long IsRegionModified(IntPtr Address, IntPtr Size, bool Reset) + { + IntPtr[] Addresses = new IntPtr[1]; + + long Count = Addresses.Length; + + long Granularity; + + int Flags = Reset ? WRITE_WATCH_FLAG_RESET : 0; + + GetWriteWatch( + Flags, + Address, + Size, + Addresses, + &Count, + &Granularity); + + return Count != 0 ? Granularity : 0; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/Gpu/NvGpu.cs b/Ryujinx.Core/Gpu/NvGpu.cs index 71df76ff3..0fca7b992 100644 --- a/Ryujinx.Core/Gpu/NvGpu.cs +++ b/Ryujinx.Core/Gpu/NvGpu.cs @@ -3,7 +3,7 @@ using System.Threading; namespace Ryujinx.Core.Gpu { - public class NvGpu + class NvGpu { public IGalRenderer Renderer { get; private set; } diff --git a/Ryujinx.Core/Gpu/NvGpuBufferType.cs b/Ryujinx.Core/Gpu/NvGpuBufferType.cs new file mode 100644 index 000000000..6c4e7d10b --- /dev/null +++ b/Ryujinx.Core/Gpu/NvGpuBufferType.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Core.Gpu +{ + enum NvGpuBufferType + { + Index, + Vertex, + Texture + } +} \ No newline at end of file diff --git a/Ryujinx.Core/Gpu/NvGpuEngine2d.cs b/Ryujinx.Core/Gpu/NvGpuEngine2d.cs index 88395b7ab..c419355e8 100644 --- a/Ryujinx.Core/Gpu/NvGpuEngine2d.cs +++ b/Ryujinx.Core/Gpu/NvGpuEngine2d.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; namespace Ryujinx.Core.Gpu { - public class NvGpuEngine2d : INvGpuEngine + class NvGpuEngine2d : INvGpuEngine { private enum CopyOperation { diff --git a/Ryujinx.Core/Gpu/NvGpuEngine3d.cs b/Ryujinx.Core/Gpu/NvGpuEngine3d.cs index b827debe2..76d21f12c 100644 --- a/Ryujinx.Core/Gpu/NvGpuEngine3d.cs +++ b/Ryujinx.Core/Gpu/NvGpuEngine3d.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Ryujinx.Core.Gpu { - public class NvGpuEngine3d : INvGpuEngine + class NvGpuEngine3d : INvGpuEngine { public int[] Registers { get; private set; } @@ -261,6 +261,8 @@ namespace Ryujinx.Core.Gpu long TextureAddress = Vmm.ReadInt64(TicPosition + 4) & 0xffffffffffff; + long Tag = TextureAddress; + TextureAddress = Vmm.GetPhysicalAddress(TextureAddress); if (IsFrameBufferPosition(TextureAddress)) @@ -273,10 +275,25 @@ namespace Ryujinx.Core.Gpu } else { - GalTexture Texture = TextureFactory.MakeTexture(Gpu, Vmm, TicPosition); + GalTexture NewTexture = TextureFactory.MakeTexture(Vmm, TicPosition); - Gpu.Renderer.SetTextureAndSampler(TexIndex, Texture, Sampler); - Gpu.Renderer.BindTexture(TexIndex); + long Size = (uint)TextureHelper.GetTextureSize(NewTexture); + + if (Gpu.Renderer.TryGetCachedTexture(Tag, Size, out GalTexture Texture)) + { + if (NewTexture.Equals(Texture) && !Vmm.IsRegionModified(Tag, Size, NvGpuBufferType.Texture)) + { + Gpu.Renderer.BindTexture(Tag, TexIndex); + + return; + } + } + + byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition); + + Gpu.Renderer.SetTextureAndSampler(Tag, Data, NewTexture, Sampler); + + Gpu.Renderer.BindTexture(Tag, TexIndex); } } @@ -330,11 +347,18 @@ namespace Ryujinx.Core.Gpu if (IndexSize != 0) { - int BufferSize = IndexCount * IndexSize; + int IbSize = IndexCount * IndexSize; - byte[] Data = Vmm.ReadBytes(IndexPosition, BufferSize); + bool IboCached = Gpu.Renderer.IsIboCached(IndexPosition, (uint)IbSize); - Gpu.Renderer.SetIndexArray(Data, IndexFormat); + if (!IboCached || Vmm.IsRegionModified(IndexPosition, (uint)IbSize, NvGpuBufferType.Index)) + { + byte[] Data = Vmm.ReadBytes(IndexPosition, (uint)IbSize); + + Gpu.Renderer.CreateIbo(IndexPosition, Data); + } + + Gpu.Renderer.SetIndexArray(IndexPosition, IbSize, IndexFormat); } List[] Attribs = new List[32]; @@ -359,10 +383,17 @@ namespace Ryujinx.Core.Gpu ((Packed >> 31) & 0x1) != 0)); } + int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst); + int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount); + + int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); + for (int Index = 0; Index < 32; Index++) { - int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst); - int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount); + if (Attribs[Index] == null) + { + continue; + } int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4); @@ -378,39 +409,38 @@ namespace Ryujinx.Core.Gpu int Stride = Control & 0xfff; - long Size = 0; + long VbSize = 0; if (IndexCount != 0) { - Size = (VertexEndPos - VertexPosition) + 1; + VbSize = (VertexEndPos - VertexPosition) + 1; } else { - Size = VertexCount; + VbSize = VertexCount * Stride; } - //TODO: Support cases where the Stride is 0. - //In this case, we need to use the size of the attribute. - Size *= Stride; + bool VboCached = Gpu.Renderer.IsVboCached(VertexPosition, VbSize); - byte[] Data = Vmm.ReadBytes(VertexPosition, Size); - - GalVertexAttrib[] AttribArray = Attribs[Index]?.ToArray() ?? new GalVertexAttrib[0]; - - Gpu.Renderer.SetVertexArray(Index, Stride, Data, AttribArray); - - int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl); - - GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); - - if (IndexCount != 0) + if (!VboCached || Vmm.IsRegionModified(VertexPosition, VbSize, NvGpuBufferType.Vertex)) { - Gpu.Renderer.DrawElements(Index, IndexFirst, PrimType); - } - else - { - Gpu.Renderer.DrawArrays(Index, VertexFirst, VertexCount, PrimType); + byte[] Data = Vmm.ReadBytes(VertexPosition, VbSize); + + Gpu.Renderer.CreateVbo(VertexPosition, Data); } + + Gpu.Renderer.SetVertexArray(Index, Stride, VertexPosition, Attribs[Index].ToArray()); + } + + GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff); + + if (IndexCount != 0) + { + Gpu.Renderer.DrawElements(IndexPosition, IndexFirst, PrimType); + } + else + { + Gpu.Renderer.DrawArrays(VertexFirst, VertexCount, PrimType); } } diff --git a/Ryujinx.Core/Gpu/NvGpuFifo.cs b/Ryujinx.Core/Gpu/NvGpuFifo.cs index d0e6fc14a..6a309b182 100644 --- a/Ryujinx.Core/Gpu/NvGpuFifo.cs +++ b/Ryujinx.Core/Gpu/NvGpuFifo.cs @@ -2,7 +2,7 @@ using System.Collections.Concurrent; namespace Ryujinx.Core.Gpu { - public class NvGpuFifo + class NvGpuFifo { private const int MacrosCount = 0x80; private const int MacroIndexMask = MacrosCount - 1; diff --git a/Ryujinx.Core/Gpu/NvGpuPBEntry.cs b/Ryujinx.Core/Gpu/NvGpuPBEntry.cs index ebf35b9e4..d640656bd 100644 --- a/Ryujinx.Core/Gpu/NvGpuPBEntry.cs +++ b/Ryujinx.Core/Gpu/NvGpuPBEntry.cs @@ -3,7 +3,7 @@ using System.Collections.ObjectModel; namespace Ryujinx.Core.Gpu { - public struct NvGpuPBEntry + struct NvGpuPBEntry { public int Method { get; private set; } diff --git a/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs b/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs index d55886559..867bbe987 100644 --- a/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs +++ b/Ryujinx.Core/Gpu/NvGpuPushBuffer.cs @@ -3,7 +3,7 @@ using System.IO; namespace Ryujinx.Core.Gpu { - public static class NvGpuPushBuffer + static class NvGpuPushBuffer { private enum SubmissionMode { diff --git a/Ryujinx.Core/Gpu/NvGpuVmm.cs b/Ryujinx.Core/Gpu/NvGpuVmm.cs index 09553b8ad..73fc2661a 100644 --- a/Ryujinx.Core/Gpu/NvGpuVmm.cs +++ b/Ryujinx.Core/Gpu/NvGpuVmm.cs @@ -4,24 +4,24 @@ using System.Collections.Concurrent; namespace Ryujinx.Core.Gpu { - public class NvGpuVmm : IAMemory, IGalMemory + class NvGpuVmm : IAMemory, IGalMemory { public const long AddrSize = 1L << 40; - private const int PTLvl0Bits = 14; - private const int PTLvl1Bits = 14; - private const int PTPageBits = 12; + private const int PTLvl0Bits = 14; + private const int PTLvl1Bits = 14; + private const int PTPageBits = 12; - private const int PTLvl0Size = 1 << PTLvl0Bits; - private const int PTLvl1Size = 1 << PTLvl1Bits; - public const int PageSize = 1 << PTPageBits; + private const int PTLvl0Size = 1 << PTLvl0Bits; + private const int PTLvl1Size = 1 << PTLvl1Bits; + public const int PageSize = 1 << PTPageBits; - private const int PTLvl0Mask = PTLvl0Size - 1; - private const int PTLvl1Mask = PTLvl1Size - 1; - public const int PageMask = PageSize - 1; + private const int PTLvl0Mask = PTLvl0Size - 1; + private const int PTLvl1Mask = PTLvl1Size - 1; + public const int PageMask = PageSize - 1; - private const int PTLvl0Bit = PTPageBits + PTLvl1Bits; - private const int PTLvl1Bit = PTPageBits; + private const int PTLvl0Bit = PTPageBits + PTLvl1Bits; + private const int PTLvl1Bit = PTPageBits; public AMemory Memory { get; private set; } @@ -37,6 +37,8 @@ namespace Ryujinx.Core.Gpu private ConcurrentDictionary Maps; + private NvGpuVmmCache Cache; + private const long PteUnmapped = -1; private const long PteReserved = -2; @@ -48,6 +50,8 @@ namespace Ryujinx.Core.Gpu Maps = new ConcurrentDictionary(); + Cache = new NvGpuVmmCache(); + PageTable = new long[PTLvl0Size][]; } @@ -270,6 +274,13 @@ namespace Ryujinx.Core.Gpu PageTable[L0][L1] = TgtAddr; } + public bool IsRegionModified(long Position, long Size, NvGpuBufferType BufferType) + { + long PA = GetPhysicalAddress(Position); + + return Cache.IsRegionModified(Memory, BufferType, Position, PA, Size); + } + public byte ReadByte(long Position) { Position = GetPhysicalAddress(Position); diff --git a/Ryujinx.Core/Gpu/NvGpuVmmCache.cs b/Ryujinx.Core/Gpu/NvGpuVmmCache.cs new file mode 100644 index 000000000..753118e95 --- /dev/null +++ b/Ryujinx.Core/Gpu/NvGpuVmmCache.cs @@ -0,0 +1,209 @@ +using ChocolArm64.Memory; +using System; +using System.Collections.Generic; + +namespace Ryujinx.Core.Gpu +{ + class NvGpuVmmCache + { + private const int MaxCpCount = 10000; + private const int MaxCpTimeDelta = 60000; + + private class CachedPage + { + private List<(long Start, long End)> Regions; + + public LinkedListNode Node { get; set; } + + public int Count => Regions.Count; + + public int Timestamp { get; private set; } + + public long PABase { get; private set; } + + public NvGpuBufferType BufferType { get; private set; } + + public CachedPage(long PABase, NvGpuBufferType BufferType) + { + this.PABase = PABase; + this.BufferType = BufferType; + + Regions = new List<(long, long)>(); + } + + public bool AddRange(long Start, long End) + { + for (int Index = 0; Index < Regions.Count; Index++) + { + (long RgStart, long RgEnd) = Regions[Index]; + + if (Start >= RgStart && End <= RgEnd) + { + return false; + } + + if (Start <= RgEnd && RgStart <= End) + { + long MinStart = Math.Min(RgStart, Start); + long MaxEnd = Math.Max(RgEnd, End); + + Regions[Index] = (MinStart, MaxEnd); + + Timestamp = Environment.TickCount; + + return true; + } + } + + Regions.Add((Start, End)); + + Timestamp = Environment.TickCount; + + return true; + } + } + + private Dictionary Cache; + + private LinkedList SortedCache; + + private int CpCount; + + public NvGpuVmmCache() + { + Cache = new Dictionary(); + + SortedCache = new LinkedList(); + } + + public bool IsRegionModified( + AMemory Memory, + NvGpuBufferType BufferType, + long VA, + long PA, + long Size) + { + ClearCachedPagesIfNeeded(); + + long PageSize = Memory.GetHostPageSize(); + + long Mask = PageSize - 1; + + long VAEnd = VA + Size; + long PAEnd = PA + Size; + + bool RegMod = false; + + while (VA < VAEnd) + { + long Key = VA & ~Mask; + long PABase = PA & ~Mask; + + long VAPgEnd = Math.Min((VA + PageSize) & ~Mask, VAEnd); + long PAPgEnd = Math.Min((PA + PageSize) & ~Mask, PAEnd); + + bool IsCached = Cache.TryGetValue(Key, out CachedPage Cp); + + bool PgReset = false; + + if (!IsCached) + { + Cp = new CachedPage(PABase, BufferType); + + Cache.Add(Key, Cp); + } + else + { + CpCount -= Cp.Count; + + SortedCache.Remove(Cp.Node); + + if (Cp.PABase != PABase || + Cp.BufferType != BufferType) + { + PgReset = true; + } + } + + PgReset |= Memory.IsRegionModified(PA, PAPgEnd - PA) && IsCached; + + if (PgReset) + { + Cp = new CachedPage(PABase, BufferType); + + Cache[Key] = Cp; + } + + Cp.Node = SortedCache.AddLast(Key); + + RegMod |= Cp.AddRange(VA, VAPgEnd); + + CpCount += Cp.Count; + + VA = VAPgEnd; + PA = PAPgEnd; + } + + return RegMod; + } + + private void ClearCachedPagesIfNeeded() + { + if (CpCount <= MaxCpCount) + { + return; + } + + int Timestamp = Environment.TickCount; + + int TimeDelta; + + do + { + if (!TryPopOldestCachedPageKey(Timestamp, out long Key)) + { + break; + } + + CachedPage Cp = Cache[Key]; + + Cache.Remove(Key); + + CpCount -= Cp.Count; + + TimeDelta = RingDelta(Cp.Timestamp, Timestamp); + } + while (CpCount > (MaxCpCount >> 1) || (uint)TimeDelta > (uint)MaxCpTimeDelta); + } + + private bool TryPopOldestCachedPageKey(int Timestamp, out long Key) + { + LinkedListNode Node = SortedCache.First; + + if (Node == null) + { + Key = 0; + + return false; + } + + SortedCache.Remove(Node); + + Key = Node.Value; + + return true; + } + + private int RingDelta(int Old, int New) + { + if ((uint)New < (uint)Old) + { + return New + (~Old + 1); + } + else + { + return New - Old; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Core/Gpu/Texture.cs b/Ryujinx.Core/Gpu/Texture.cs index 39a35059b..022df83fe 100644 --- a/Ryujinx.Core/Gpu/Texture.cs +++ b/Ryujinx.Core/Gpu/Texture.cs @@ -2,7 +2,7 @@ using Ryujinx.Graphics.Gal; namespace Ryujinx.Core.Gpu { - public struct Texture + struct Texture { public long Position { get; private set; } diff --git a/Ryujinx.Core/Gpu/TextureFactory.cs b/Ryujinx.Core/Gpu/TextureFactory.cs index 5206c125b..9262b9478 100644 --- a/Ryujinx.Core/Gpu/TextureFactory.cs +++ b/Ryujinx.Core/Gpu/TextureFactory.cs @@ -5,7 +5,7 @@ namespace Ryujinx.Core.Gpu { static class TextureFactory { - public static GalTexture MakeTexture(NvGpu Gpu, NvGpuVmm Vmm, long TicPosition) + public static GalTexture MakeTexture(NvGpuVmm Vmm, long TicPosition) { int[] Tic = ReadWords(Vmm, TicPosition, 8); @@ -16,6 +16,25 @@ namespace Ryujinx.Core.Gpu GalTextureSource ZSource = (GalTextureSource)((Tic[0] >> 25) & 7); GalTextureSource WSource = (GalTextureSource)((Tic[0] >> 28) & 7); + int Width = (Tic[4] & 0xffff) + 1; + int Height = (Tic[5] & 0xffff) + 1; + + return new GalTexture( + Width, + Height, + Format, + XSource, + YSource, + ZSource, + WSource); + } + + public static byte[] GetTextureData(NvGpuVmm Vmm, long TicPosition) + { + int[] Tic = ReadWords(Vmm, TicPosition, 8); + + GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f); + long TextureAddress = (uint)Tic[1]; TextureAddress |= (long)((ushort)Tic[2]) << 32; @@ -40,17 +59,7 @@ namespace Ryujinx.Core.Gpu Swizzle, Format); - byte[] Data = TextureReader.Read(Vmm, Texture); - - return new GalTexture( - Data, - Width, - Height, - Format, - XSource, - YSource, - ZSource, - WSource); + return TextureReader.Read(Vmm, Texture); } public static GalTextureSampler MakeSampler(NvGpu Gpu, NvGpuVmm Vmm, long TscPosition) diff --git a/Ryujinx.Core/Gpu/TextureHelper.cs b/Ryujinx.Core/Gpu/TextureHelper.cs index f0ebc1f04..1863bb777 100644 --- a/Ryujinx.Core/Gpu/TextureHelper.cs +++ b/Ryujinx.Core/Gpu/TextureHelper.cs @@ -1,4 +1,5 @@ using ChocolArm64.Memory; +using Ryujinx.Graphics.Gal; using System; namespace Ryujinx.Core.Gpu @@ -21,6 +22,43 @@ namespace Ryujinx.Core.Gpu throw new NotImplementedException(Texture.Swizzle.ToString()); } + public static int GetTextureSize(GalTexture Texture) + { + switch (Texture.Format) + { + case GalTextureFormat.R32G32B32A32: return Texture.Width * Texture.Height * 16; + case GalTextureFormat.R16G16B16A16: return Texture.Width * Texture.Height * 8; + case GalTextureFormat.A8B8G8R8: return Texture.Width * Texture.Height * 4; + case GalTextureFormat.R32: return Texture.Width * Texture.Height * 4; + case GalTextureFormat.A1B5G5R5: return Texture.Width * Texture.Height * 2; + case GalTextureFormat.B5G6R5: return Texture.Width * Texture.Height * 2; + case GalTextureFormat.G8R8: return Texture.Width * Texture.Height * 2; + case GalTextureFormat.R8: return Texture.Width * Texture.Height; + + case GalTextureFormat.BC1: + case GalTextureFormat.BC4: + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + return W * H * 8; + } + + case GalTextureFormat.BC2: + case GalTextureFormat.BC3: + case GalTextureFormat.BC5: + case GalTextureFormat.Astc2D4x4: + { + int W = (Texture.Width + 3) / 4; + int H = (Texture.Height + 3) / 4; + + return W * H * 16; + } + } + + throw new NotImplementedException(Texture.Format.ToString()); + } + public static (AMemory Memory, long Position) GetMemoryAndPosition( IAMemory Memory, long Position) diff --git a/Ryujinx.Core/Gpu/TextureReader.cs b/Ryujinx.Core/Gpu/TextureReader.cs index ae3b00007..acd17c5dc 100644 --- a/Ryujinx.Core/Gpu/TextureReader.cs +++ b/Ryujinx.Core/Gpu/TextureReader.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Core.Gpu { - public static class TextureReader + static class TextureReader { public static byte[] Read(IAMemory Memory, Texture Texture) { diff --git a/Ryujinx.Core/Gpu/TextureSwizzle.cs b/Ryujinx.Core/Gpu/TextureSwizzle.cs index 3214f45f8..fbca40e12 100644 --- a/Ryujinx.Core/Gpu/TextureSwizzle.cs +++ b/Ryujinx.Core/Gpu/TextureSwizzle.cs @@ -1,6 +1,6 @@ namespace Ryujinx.Core.Gpu { - public enum TextureSwizzle + enum TextureSwizzle { _1dBuffer = 0, PitchColorKey = 1, diff --git a/Ryujinx.Core/Gpu/TextureWriter.cs b/Ryujinx.Core/Gpu/TextureWriter.cs index 125bb8c4f..686d0dce6 100644 --- a/Ryujinx.Core/Gpu/TextureWriter.cs +++ b/Ryujinx.Core/Gpu/TextureWriter.cs @@ -4,7 +4,7 @@ using System; namespace Ryujinx.Core.Gpu { - public static class TextureWriter + static class TextureWriter { public static void Write(IAMemory Memory, Texture Texture, byte[] Data) { diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs index 8aa26dd3f..b6346f4e8 100644 --- a/Ryujinx.Core/OsHle/Kernel/SvcThread.cs +++ b/Ryujinx.Core/OsHle/Kernel/SvcThread.cs @@ -1,6 +1,7 @@ using ChocolArm64.State; using Ryujinx.Core.Logging; using Ryujinx.Core.OsHle.Handles; +using System; using System.Threading; using static Ryujinx.Core.OsHle.ErrorCode; diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalTexture.cs index 75aef3072..2c1be65b2 100644 --- a/Ryujinx.Graphics/Gal/GalTexture.cs +++ b/Ryujinx.Graphics/Gal/GalTexture.cs @@ -2,8 +2,6 @@ namespace Ryujinx.Graphics.Gal { public struct GalTexture { - public byte[] Data; - public int Width; public int Height; @@ -15,7 +13,6 @@ namespace Ryujinx.Graphics.Gal public GalTextureSource WSource; public GalTexture( - byte[] Data, int Width, int Height, GalTextureFormat Format, @@ -24,7 +21,6 @@ namespace Ryujinx.Graphics.Gal GalTextureSource ZSource, GalTextureSource WSource) { - this.Data = Data; this.Width = Width; this.Height = Height; this.Format = Format; diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs index 79c20e0e1..b8f83469b 100644 --- a/Ryujinx.Graphics/Gal/IGalRenderer.cs +++ b/Ryujinx.Graphics/Gal/IGalRenderer.cs @@ -49,13 +49,21 @@ namespace Ryujinx.Graphics.Gal //Rasterizer void ClearBuffers(int RtIndex, GalClearBufferFlags Flags); - void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs); + bool IsVboCached(long Tag, long DataSize); - void SetIndexArray(byte[] Buffer, GalIndexFormat Format); + bool IsIboCached(long Tag, long DataSize); - void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType); + void CreateVbo(long Tag, byte[] Buffer); - void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType); + void CreateIbo(long Tag, byte[] Buffer); + + void SetVertexArray(int VbIndex, int Stride, long VboTag, GalVertexAttrib[] Attribs); + + void SetIndexArray(long Tag, int Size, GalIndexFormat Format); + + void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType); + + void DrawElements(long IboTag, int First, GalPrimitiveType PrimType); //Shader void CreateShader(IGalMemory Memory, long Tag, GalShaderType Type); @@ -73,8 +81,10 @@ namespace Ryujinx.Graphics.Gal void BindProgram(); //Texture - void SetTextureAndSampler(int Index, GalTexture Texture, GalTextureSampler Sampler); + void SetTextureAndSampler(long Tag, byte[] Data, GalTexture Texture, GalTextureSampler Sampler); - void BindTexture(int Index); + bool TryGetCachedTexture(long Tag, long DataSize, out GalTexture Texture); + + void BindTexture(long Tag, int Index); } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs b/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs new file mode 100644 index 000000000..acd8d72f6 --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/DeleteValueCallback.cs @@ -0,0 +1,4 @@ +namespace Ryujinx.Graphics.Gal.OpenGL +{ + delegate void DeleteValue(T Value); +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs new file mode 100644 index 000000000..06d76b8bd --- /dev/null +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLCachedResource.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.Generic; + +namespace Ryujinx.Graphics.Gal.OpenGL +{ + class OGLCachedResource + { + public delegate void DeleteValue(T Value); + + private const int MaxTimeDelta = 5 * 60000; + private const int MaxRemovalsPerRun = 10; + + private struct CacheBucket + { + public T Value { get; private set; } + + public LinkedListNode Node { get; private set; } + + public long DataSize { get; private set; } + + public int Timestamp { get; private set; } + + public CacheBucket(T Value, long DataSize, LinkedListNode Node) + { + this.Value = Value; + this.DataSize = DataSize; + this.Node = Node; + + Timestamp = Environment.TickCount; + } + } + + private Dictionary Cache; + + private LinkedList SortedCache; + + private DeleteValue DeleteValueCallback; + + public OGLCachedResource(DeleteValue DeleteValueCallback) + { + if (DeleteValueCallback == null) + { + throw new ArgumentNullException(nameof(DeleteValueCallback)); + } + + this.DeleteValueCallback = DeleteValueCallback; + + Cache = new Dictionary(); + + SortedCache = new LinkedList(); + } + + public void AddOrUpdate(long Key, T Value, long Size) + { + ClearCacheIfNeeded(); + + LinkedListNode Node = SortedCache.AddLast(Key); + + CacheBucket NewBucket = new CacheBucket(Value, Size, Node); + + if (Cache.TryGetValue(Key, out CacheBucket Bucket)) + { + DeleteValueCallback(Bucket.Value); + + SortedCache.Remove(Bucket.Node); + + Cache[Key] = NewBucket; + } + else + { + Cache.Add(Key, NewBucket); + } + } + + public bool TryGetValue(long Key, out T Value) + { + if (Cache.TryGetValue(Key, out CacheBucket Bucket)) + { + Value = Bucket.Value; + + 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() + { + int Timestamp = Environment.TickCount; + + int Count = 0; + + while (Count++ < MaxRemovalsPerRun) + { + LinkedListNode Node = SortedCache.First; + + if (Node == null) + { + break; + } + + CacheBucket Bucket = Cache[Node.Value]; + + int TimeDelta = RingDelta(Bucket.Timestamp, Timestamp); + + if ((uint)TimeDelta <= (uint)MaxTimeDelta) + { + break; + } + + SortedCache.Remove(Node); + + Cache.Remove(Node.Value); + + DeleteValueCallback(Bucket.Value); + } + } + + private int RingDelta(int Old, int New) + { + if ((uint)New < (uint)Old) + { + return New + (~Old + 1); + } + else + { + return New - Old; + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs index 5b115446e..b63c8b358 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs @@ -44,24 +44,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GalVertexAttribSize._11_11_10, VertexAttribPointerType.Int } //? }; + private int VaoHandle; + + private int[] VertexBuffers; + + private OGLCachedResource VboCache; + private OGLCachedResource IboCache; + private struct IbInfo { - public int IboHandle; public int Count; public DrawElementsType Type; } - private int VaoHandle; - - private int[] VertexBuffers; - private IbInfo IndexBuffer; public OGLRasterizer() { VertexBuffers = new int[32]; + VboCache = new OGLCachedResource(GL.DeleteBuffer); + IboCache = new OGLCachedResource(GL.DeleteBuffer); + IndexBuffer = new IbInfo(); } @@ -92,15 +97,53 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.Clear(Mask); } - public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + public bool IsVboCached(long Tag, long DataSize) { - EnsureVbInitialized(VbIndex); + return VboCache.TryGetSize(Tag, out long Size) && Size == DataSize; + } + + public bool IsIboCached(long Tag, long DataSize) + { + return IboCache.TryGetSize(Tag, out long Size) && Size == DataSize; + } + + public void CreateVbo(long Tag, byte[] Buffer) + { + int Handle = GL.GenBuffer(); + + VboCache.AddOrUpdate(Tag, Handle, (uint)Buffer.Length); IntPtr Length = new IntPtr(Buffer.Length); - GL.BindBuffer(BufferTarget.ArrayBuffer, VertexBuffers[VbIndex]); + GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); GL.BindBuffer(BufferTarget.ArrayBuffer, 0); + } + + public void CreateIbo(long Tag, byte[] Buffer) + { + int Handle = GL.GenBuffer(); + + IboCache.AddOrUpdate(Tag, Handle, (uint)Buffer.Length); + + IntPtr Length = new IntPtr(Buffer.Length); + + GL.BindBuffer(BufferTarget.ElementArrayBuffer, Handle); + GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + } + + public void SetVertexArray(int VbIndex, int Stride, long VboTag, GalVertexAttrib[] Attribs) + { + if (!VboCache.TryGetValue(VboTag, out int VboHandle)) + { + return; + } + + if (VaoHandle == 0) + { + VaoHandle = GL.GenVertexArray(); + } GL.BindVertexArray(VaoHandle); @@ -108,7 +151,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL { GL.EnableVertexAttribArray(Attrib.Index); - GL.BindBuffer(BufferTarget.ArrayBuffer, VertexBuffers[VbIndex]); + GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle); bool Unsigned = Attrib.Type == GalVertexAttribType.Unorm || @@ -139,22 +182,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.BindVertexArray(0); } - public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) + public void SetIndexArray(long Tag, int Size, GalIndexFormat Format) { - EnsureIbInitialized(); - IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format); - IndexBuffer.Count = Buffer.Length >> (int)Format; - - IntPtr Length = new IntPtr(Buffer.Length); - - GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); - GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw); - GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); + IndexBuffer.Count = Size >> (int)Format; } - public void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType) + public void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType) { if (PrimCount == 0) { @@ -166,36 +201,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, PrimCount); } - public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + public void DrawElements(long IboTag, int First, GalPrimitiveType PrimType) { + if (!IboCache.TryGetValue(IboTag, out int IboHandle)) + { + return; + } + PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType); GL.BindVertexArray(VaoHandle); - GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle); + GL.BindBuffer(BufferTarget.ElementArrayBuffer, IboHandle); GL.DrawElements(Mode, IndexBuffer.Count, IndexBuffer.Type, First); } - - private void EnsureVbInitialized(int VbIndex) - { - if (VaoHandle == 0) - { - VaoHandle = GL.GenVertexArray(); - } - - if (VertexBuffers[VbIndex] == 0) - { - VertexBuffers[VbIndex] = GL.GenBuffer(); - } - } - - private void EnsureIbInitialized() - { - if (IndexBuffer.IboHandle == 0) - { - IndexBuffer.IboHandle = GL.GenBuffer(); - } - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs index 2cbe6913d..540e47359 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs @@ -6,18 +6,38 @@ namespace Ryujinx.Graphics.Gal.OpenGL { class OGLTexture { - private int[] Textures; + private class TCE + { + public int Handle; + + public GalTexture Texture; + + public TCE(int Handle, GalTexture Texture) + { + this.Handle = Handle; + this.Texture = Texture; + } + } + + private OGLCachedResource TextureCache; public OGLTexture() { - Textures = new int[80]; + TextureCache = new OGLCachedResource(DeleteTexture); } - public void Set(int Index, GalTexture Texture) + private static void DeleteTexture(TCE CachedTexture) { - GL.ActiveTexture(TextureUnit.Texture0 + Index); + GL.DeleteTexture(CachedTexture.Handle); + } - Bind(Index); + public void Create(long Tag, byte[] Data, GalTexture Texture) + { + int Handle = GL.GenTexture(); + + TextureCache.AddOrUpdate(Tag, new TCE(Handle, Texture), (uint)Data.Length); + + GL.BindTexture(TextureTarget.Texture2D, Handle); const int Level = 0; //TODO: Support mipmap textures. const int Border = 0; @@ -33,14 +53,24 @@ namespace Ryujinx.Graphics.Gal.OpenGL Texture.Width, Texture.Height, Border, - Texture.Data.Length, - Texture.Data); + Data.Length, + Data); } else { if (Texture.Format >= GalTextureFormat.Astc2D4x4) { - Texture = ConvertAstcTextureToRgba(Texture); + int TextureBlockWidth = GetAstcBlockWidth(Texture.Format); + int TextureBlockHeight = GetAstcBlockHeight(Texture.Format); + + Data = ASTCDecoder.DecodeToRGBA8888( + Data, + TextureBlockWidth, + TextureBlockHeight, 1, + Texture.Width, + Texture.Height, 1); + + Texture.Format = GalTextureFormat.A8B8G8R8; } const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba; @@ -56,7 +86,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL Border, Format, Type, - Texture.Data); + Data); } int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Texture.XSource); @@ -70,23 +100,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA); } - private static GalTexture ConvertAstcTextureToRgba(GalTexture Texture) - { - int TextureBlockWidth = GetAstcBlockWidth(Texture.Format); - int TextureBlockHeight = GetAstcBlockHeight(Texture.Format); - - Texture.Data = ASTCDecoder.DecodeToRGBA8888( - Texture.Data, - TextureBlockWidth, - TextureBlockHeight, 1, - Texture.Width, - Texture.Height, 1); - - Texture.Format = GalTextureFormat.A8B8G8R8; - - return Texture; - } - private static int GetAstcBlockWidth(GalTextureFormat Format) { switch (Format) @@ -133,11 +146,31 @@ namespace Ryujinx.Graphics.Gal.OpenGL throw new ArgumentException(nameof(Format)); } - public void Bind(int Index) + public bool TryGetCachedTexture(long Tag, long DataSize, out GalTexture Texture) { - int Handle = EnsureTextureInitialized(Index); + if (TextureCache.TryGetSize(Tag, out long Size) && Size == DataSize) + { + if (TextureCache.TryGetValue(Tag, out TCE CachedTexture)) + { + Texture = CachedTexture.Texture; - GL.BindTexture(TextureTarget.Texture2D, Handle); + return true; + } + } + + Texture = default(GalTexture); + + return false; + } + + public void Bind(long Tag, int Index) + { + if (TextureCache.TryGetValue(Tag, out TCE CachedTexture)) + { + GL.ActiveTexture(TextureUnit.Texture0 + Index); + + GL.BindTexture(TextureTarget.Texture2D, CachedTexture.Handle); + } } public static void Set(GalTextureSampler Sampler) @@ -179,17 +212,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL return false; } - - private int EnsureTextureInitialized(int TexIndex) - { - int Handle = Textures[TexIndex]; - - if (Handle == 0) - { - Handle = Textures[TexIndex] = GL.GenTexture(); - } - - return Handle; - } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs index 69e344c74..4c4bd2cae 100644 --- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs +++ b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs @@ -156,46 +156,54 @@ namespace Ryujinx.Graphics.Gal.OpenGL ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags)); } - public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs) + public bool IsVboCached(long Tag, long DataSize) + { + return Rasterizer.IsVboCached(Tag, DataSize); + } + + public bool IsIboCached(long Tag, long DataSize) + { + return Rasterizer.IsIboCached(Tag, DataSize); + } + + public void CreateVbo(long Tag, byte[] Buffer) + { + ActionsQueue.Enqueue(() => Rasterizer.CreateVbo(Tag, Buffer)); + } + + public void CreateIbo(long Tag, byte[] Buffer) + { + ActionsQueue.Enqueue(() => Rasterizer.CreateIbo(Tag, Buffer)); + } + + public void SetVertexArray(int VbIndex, int Stride, long VboTag, GalVertexAttrib[] Attribs) { if ((uint)VbIndex > 31) { throw new ArgumentOutOfRangeException(nameof(VbIndex)); } - ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride, - Buffer ?? throw new ArgumentNullException(nameof(Buffer)), - Attribs ?? throw new ArgumentNullException(nameof(Attribs)))); - } - - public void SetIndexArray(byte[] Buffer, GalIndexFormat Format) - { - if (Buffer == null) + if (Attribs == null) { - throw new ArgumentNullException(nameof(Buffer)); + throw new ArgumentNullException(nameof(Attribs)); } - ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format)); + ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride, VboTag, Attribs)); } - public void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType) + public void SetIndexArray(long Tag, int Size, GalIndexFormat Format) { - if ((uint)VbIndex > 31) - { - throw new ArgumentOutOfRangeException(nameof(VbIndex)); - } - - ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, First, PrimCount, PrimType)); + ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Tag, Size, Format)); } - public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType) + public void DrawArrays(int First, int PrimCount, GalPrimitiveType PrimType) { - if ((uint)VbIndex > 31) - { - throw new ArgumentOutOfRangeException(nameof(VbIndex)); - } + ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(First, PrimCount, PrimType)); + } - ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType)); + public void DrawElements(long IboTag, int First, GalPrimitiveType PrimType) + { + ActionsQueue.Enqueue(() => Rasterizer.DrawElements(IboTag, First, PrimType)); } public void CreateShader(IGalMemory Memory, long Tag, GalShaderType Type) @@ -253,19 +261,24 @@ namespace Ryujinx.Graphics.Gal.OpenGL ActionsQueue.Enqueue(() => Shader.BindProgram()); } - public void SetTextureAndSampler(int Index, GalTexture Texture, GalTextureSampler Sampler) + public void SetTextureAndSampler(long Tag, byte[] Data, GalTexture Texture, GalTextureSampler Sampler) { ActionsQueue.Enqueue(() => { - this.Texture.Set(Index, Texture); + this.Texture.Create(Tag, Data, Texture); OGLTexture.Set(Sampler); }); } - public void BindTexture(int Index) + public bool TryGetCachedTexture(long Tag, long DataSize, out GalTexture Texture) { - ActionsQueue.Enqueue(() => Texture.Bind(Index)); + return this.Texture.TryGetCachedTexture(Tag, DataSize, out Texture); + } + + public void BindTexture(long Tag, int Index) + { + ActionsQueue.Enqueue(() => Texture.Bind(Tag, Index)); } } } \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Texture/BCn.cs b/Ryujinx.Graphics/Gal/Texture/BCn.cs deleted file mode 100644 index f23a86c2c..000000000 --- a/Ryujinx.Graphics/Gal/Texture/BCn.cs +++ /dev/null @@ -1,468 +0,0 @@ -using System; -using System.Drawing; - -namespace Ryujinx.Graphics.Gal.Texture -{ - static class BCn - { - public static byte[] DecodeBC1(GalTexture Texture, int Offset) - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - byte[] Output = new byte[W * H * 64]; - - SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); - - for (int Y = 0; Y < H; Y++) - { - for (int X = 0; X < W; X++) - { - int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8; - - byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true); - - int TOffset = 0; - - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - Output[OOffset + 0] = Tile[TOffset + 0]; - Output[OOffset + 1] = Tile[TOffset + 1]; - Output[OOffset + 2] = Tile[TOffset + 2]; - Output[OOffset + 3] = Tile[TOffset + 3]; - - TOffset += 4; - } - } - } - } - - return Output; - } - - public static byte[] DecodeBC2(GalTexture Texture, int Offset) - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - byte[] Output = new byte[W * H * 64]; - - SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); - - for (int Y = 0; Y < H; Y++) - { - for (int X = 0; X < W; X++) - { - int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - - byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); - - int AlphaLow = Get32(Texture.Data, IOffs + 0); - int AlphaHigh = Get32(Texture.Data, IOffs + 4); - - ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; - - int TOffset = 0; - - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - ulong Alpha = (AlphaCh >> (TY * 16 + TX * 4)) & 0xf; - - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - Output[OOffset + 0] = Tile[TOffset + 0]; - Output[OOffset + 1] = Tile[TOffset + 1]; - Output[OOffset + 2] = Tile[TOffset + 2]; - Output[OOffset + 3] = (byte)(Alpha | (Alpha << 4)); - - TOffset += 4; - } - } - } - } - - return Output; - } - - public static byte[] DecodeBC3(GalTexture Texture, int Offset) - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - byte[] Output = new byte[W * H * 64]; - - SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); - - for (int Y = 0; Y < H; Y++) - { - for (int X = 0; X < W; X++) - { - int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16; - - byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false); - - byte[] Alpha = new byte[8]; - - Alpha[0] = Texture.Data[IOffs + 0]; - Alpha[1] = Texture.Data[IOffs + 1]; - - CalculateBC3Alpha(Alpha); - - int AlphaLow = Get32(Texture.Data, IOffs + 2); - int AlphaHigh = Get16(Texture.Data, IOffs + 6); - - ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32; - - int TOffset = 0; - - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - byte AlphaPx = Alpha[(AlphaCh >> (TY * 12 + TX * 3)) & 7]; - - Output[OOffset + 0] = Tile[TOffset + 0]; - Output[OOffset + 1] = Tile[TOffset + 1]; - Output[OOffset + 2] = Tile[TOffset + 2]; - Output[OOffset + 3] = AlphaPx; - - TOffset += 4; - } - } - } - } - - return Output; - } - - public static byte[] DecodeBC4(GalTexture Texture, int Offset) - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - byte[] Output = new byte[W * H * 64]; - - SwizzleAddr Swizzle = new SwizzleAddr(W, H, 8); - - for (int Y = 0; Y < H; Y++) - { - for (int X = 0; X < W; X++) - { - int IOffs = Swizzle.GetSwizzledAddress64(X, Y) * 8; - - byte[] Red = new byte[8]; - - Red[0] = Texture.Data[IOffs + 0]; - Red[1] = Texture.Data[IOffs + 1]; - - CalculateBC3Alpha(Red); - - int RedLow = Get32(Texture.Data, IOffs + 2); - int RedHigh = Get16(Texture.Data, IOffs + 6); - - ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; - - int TOffset = 0; - - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - byte RedPx = Red[(RedCh >> (TY * 12 + TX * 3)) & 7]; - - Output[OOffset + 0] = RedPx; - Output[OOffset + 1] = RedPx; - Output[OOffset + 2] = RedPx; - Output[OOffset + 3] = 0xff; - - TOffset += 4; - } - } - } - } - - return Output; - } - - public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm) - { - int W = (Texture.Width + 3) / 4; - int H = (Texture.Height + 3) / 4; - - byte[] Output = new byte[W * H * 64]; - - SwizzleAddr Swizzle = new SwizzleAddr(W, H, 4); - - for (int Y = 0; Y < H; Y++) - { - for (int X = 0; X < W; X++) - { - int IOffs = Swizzle.GetSwizzledAddress128(X, Y) * 16; - - byte[] Red = new byte[8]; - byte[] Green = new byte[8]; - - Red[0] = Texture.Data[IOffs + 0]; - Red[1] = Texture.Data[IOffs + 1]; - - Green[0] = Texture.Data[IOffs + 8]; - Green[1] = Texture.Data[IOffs + 9]; - - if (SNorm) - { - CalculateBC3AlphaS(Red); - CalculateBC3AlphaS(Green); - } - else - { - CalculateBC3Alpha(Red); - CalculateBC3Alpha(Green); - } - - int RedLow = Get32(Texture.Data, IOffs + 2); - int RedHigh = Get16(Texture.Data, IOffs + 6); - - int GreenLow = Get32(Texture.Data, IOffs + 10); - int GreenHigh = Get16(Texture.Data, IOffs + 14); - - ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32; - ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32; - - int TOffset = 0; - - if (SNorm) - { - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int Shift = TY * 12 + TX * 3; - - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - byte RedPx = Red [(RedCh >> Shift) & 7]; - byte GreenPx = Green[(GreenCh >> Shift) & 7]; - - RedPx += 0x80; - GreenPx += 0x80; - - float NX = (RedPx / 255f) * 2 - 1; - float NY = (GreenPx / 255f) * 2 - 1; - - float NZ = (float)Math.Sqrt(1 - (NX * NX + NY * NY)); - - Output[OOffset + 0] = Clamp((NZ + 1) * 0.5f); - Output[OOffset + 1] = Clamp((NY + 1) * 0.5f); - Output[OOffset + 2] = Clamp((NX + 1) * 0.5f); - Output[OOffset + 3] = 0xff; - - TOffset += 4; - } - } - } - else - { - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int Shift = TY * 12 + TX * 3; - - int OOffset = (X * 4 + TX + (Y * 4 + TY) * W * 4) * 4; - - byte RedPx = Red [(RedCh >> Shift) & 7]; - byte GreenPx = Green[(GreenCh >> Shift) & 7]; - - Output[OOffset + 0] = RedPx; - Output[OOffset + 1] = RedPx; - Output[OOffset + 2] = RedPx; - Output[OOffset + 3] = GreenPx; - - TOffset += 4; - } - } - } - } - } - - return Output; - } - - private static byte Clamp(float Value) - { - if (Value > 1) - { - return 0xff; - } - else if (Value < 0) - { - return 0; - } - else - { - return (byte)(Value * 0xff); - } - } - - private static void CalculateBC3Alpha(byte[] Alpha) - { - for (int i = 2; i < 8; i++) - { - if (Alpha[0] > Alpha[1]) - { - Alpha[i] = (byte)(((8 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); - } - else if (i < 6) - { - Alpha[i] = (byte)(((6 - i) * Alpha[0] + (i - 1) * Alpha[1]) / 7); - } - else if (i == 6) - { - Alpha[i] = 0; - } - else /* i == 7 */ - { - Alpha[i] = 0xff; - } - } - } - - private static void CalculateBC3AlphaS(byte[] Alpha) - { - for (int i = 2; i < 8; i++) - { - if ((sbyte)Alpha[0] > (sbyte)Alpha[1]) - { - Alpha[i] = (byte)(((8 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); - } - else if (i < 6) - { - Alpha[i] = (byte)(((6 - i) * (sbyte)Alpha[0] + (i - 1) * (sbyte)Alpha[1]) / 7); - } - else if (i == 6) - { - Alpha[i] = 0x80; - } - else /* i == 7 */ - { - Alpha[i] = 0x7f; - } - } - } - - private static byte[] BCnDecodeTile( - byte[] Input, - int Offset, - bool IsBC1) - { - Color[] CLUT = new Color[4]; - - int c0 = Get16(Input, Offset + 0); - int c1 = Get16(Input, Offset + 2); - - CLUT[0] = DecodeRGB565(c0); - CLUT[1] = DecodeRGB565(c1); - CLUT[2] = CalculateCLUT2(CLUT[0], CLUT[1], c0, c1, IsBC1); - CLUT[3] = CalculateCLUT3(CLUT[0], CLUT[1], c0, c1, IsBC1); - - int Indices = Get32(Input, Offset + 4); - - int IdxShift = 0; - - byte[] Output = new byte[4 * 4 * 4]; - - int OOffset = 0; - - for (int TY = 0; TY < 4; TY++) - { - for (int TX = 0; TX < 4; TX++) - { - int Idx = (Indices >> IdxShift) & 3; - - IdxShift += 2; - - Color Pixel = CLUT[Idx]; - - Output[OOffset + 0] = Pixel.R; - Output[OOffset + 1] = Pixel.G; - Output[OOffset + 2] = Pixel.B; - Output[OOffset + 3] = Pixel.A; - - OOffset += 4; - } - } - - return Output; - } - - private static Color CalculateCLUT2(Color C0, Color C1, int c0, int c1, bool IsBC1) - { - if (c0 > c1 || !IsBC1) - { - return Color.FromArgb( - (2 * C0.R + C1.R) / 3, - (2 * C0.G + C1.G) / 3, - (2 * C0.B + C1.B) / 3); - } - else - { - return Color.FromArgb( - (C0.R + C1.R) / 2, - (C0.G + C1.G) / 2, - (C0.B + C1.B) / 2); - } - } - - private static Color CalculateCLUT3(Color C0, Color C1, int c0, int c1, bool IsBC1) - { - if (c0 > c1 || !IsBC1) - { - return - Color.FromArgb( - (2 * C1.R + C0.R) / 3, - (2 * C1.G + C0.G) / 3, - (2 * C1.B + C0.B) / 3); - } - - return Color.Transparent; - } - - private static Color DecodeRGB565(int Value) - { - int B = ((Value >> 0) & 0x1f) << 3; - int G = ((Value >> 5) & 0x3f) << 2; - int R = ((Value >> 11) & 0x1f) << 3; - - return Color.FromArgb( - R | (R >> 5), - G | (G >> 6), - B | (B >> 5)); - } - - private static int Get16(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8; - } - - private static int Get32(byte[] Data, int Address) - { - return - Data[Address + 0] << 0 | - Data[Address + 1] << 8 | - Data[Address + 2] << 16 | - Data[Address + 3] << 24; - } - } -} \ No newline at end of file diff --git a/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs deleted file mode 100644 index b67b841bc..000000000 --- a/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Texture -{ - class SwizzleAddr - { - private int Width; - - private int XB; - private int YB; - - public SwizzleAddr(int Width, int Height, int Pad) - { - int W = Pow2RoundUp(Width); - int H = Pow2RoundUp(Height); - - XB = CountZeros(W); - YB = CountZeros(H); - - int HH = H >> 1; - - if (!IsPow2(Height) && Height <= HH + HH / 3 && YB > 3) - { - YB--; - } - - this.Width = RoundSize(Width, Pad); - } - - private static int Pow2RoundUp(int Value) - { - Value--; - - Value |= (Value >> 1); - Value |= (Value >> 2); - Value |= (Value >> 4); - Value |= (Value >> 8); - Value |= (Value >> 16); - - return ++Value; - } - - private static bool IsPow2(int Value) - { - return Value != 0 && (Value & (Value - 1)) == 0; - } - - private static int CountZeros(int Value) - { - int Count = 0; - - for (int i = 0; i < 32; i++) - { - if ((Value & (1 << i)) != 0) - { - break; - } - - Count++; - } - - return Count; - } - - private static int RoundSize(int Size, int Pad) - { - int Mask = Pad - 1; - - if ((Size & Mask) != 0) - { - Size &= ~Mask; - Size += Pad; - } - - return Size; - } - - public int GetSwizzledAddress8(int X, int Y) - { - return GetSwizzledAddress(X, Y, 4); - } - - public int GetSwizzledAddress16(int X, int Y) - { - return GetSwizzledAddress(X, Y, 3); - } - - public int GetSwizzledAddress32(int X, int Y) - { - return GetSwizzledAddress(X, Y, 2); - } - - public int GetSwizzledAddress64(int X, int Y) - { - return GetSwizzledAddress(X, Y, 1); - } - - public int GetSwizzledAddress128(int X, int Y) - { - return GetSwizzledAddress(X, Y, 0); - } - - private int GetSwizzledAddress(int X, int Y, int XBase) - { - /* - * Examples of patterns: - * x x y x y y x y 0 0 0 0 64 x 64 dxt5 - * x x x x x y y y y x y y x y 0 0 0 0 512 x 512 dxt5 - * y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5 - * y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1 - * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888 - * - * Read from right to left, LSB first. - */ - int XCnt = XBase; - int YCnt = 1; - int XUsed = 0; - int YUsed = 0; - int Address = 0; - - while (XUsed < XBase + 2 && XUsed + XCnt < XB) - { - int XMask = (1 << XCnt) - 1; - int YMask = (1 << YCnt) - 1; - - Address |= (X & XMask) << XUsed + YUsed; - Address |= (Y & YMask) << XUsed + YUsed + XCnt; - - X >>= XCnt; - Y >>= YCnt; - - XUsed += XCnt; - YUsed += YCnt; - - XCnt = Math.Min(XB - XUsed, 1); - YCnt = Math.Min(YB - YUsed, YCnt << 1); - } - - Address |= (X + Y * (Width >> XUsed)) << (XUsed + YUsed); - - return Address; - } - } -} diff --git a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs deleted file mode 100644 index 4e50db51d..000000000 --- a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; - -namespace Ryujinx.Graphics.Gal.Texture -{ - static class TextureDecoder - { - public static byte[] Decode(GalTexture Texture) - { - switch (Texture.Format) - { - case GalTextureFormat.BC1: return BCn.DecodeBC1(Texture, 0); - case GalTextureFormat.BC2: return BCn.DecodeBC2(Texture, 0); - case GalTextureFormat.BC3: return BCn.DecodeBC3(Texture, 0); - } - - throw new NotImplementedException(Texture.Format.ToString()); - } - } -} \ No newline at end of file