gdk %!s(int64=6) %!d(string=hai) anos
pai
achega
1876b346fe
Modificáronse 100 ficheiros con 6250 adicións e 370 borrados
  1. 2 5
      ARMeilleure/Instructions/InstEmitMemoryHelper.cs
  2. 0 37
      ARMeilleure/Memory/IMemory.cs
  3. 0 24
      ARMeilleure/Memory/MemoryManagement.cs
  4. 0 37
      ARMeilleure/Memory/MemoryManagementWindows.cs
  5. 28 173
      ARMeilleure/Memory/MemoryManager.cs
  6. 19 2
      Ryujinx.Common/PerformanceCounter.cs
  7. 32 0
      Ryujinx.Graphics.GAL/Blend/BlendDescriptor.cs
  8. 25 0
      Ryujinx.Graphics.GAL/Blend/BlendFactor.cs
  9. 11 0
      Ryujinx.Graphics.GAL/Blend/BlendOp.cs
  10. 21 0
      Ryujinx.Graphics.GAL/BufferRange.cs
  11. 12 0
      Ryujinx.Graphics.GAL/Capabilities.cs
  12. 18 0
      Ryujinx.Graphics.GAL/Color/ColorF.cs
  13. 18 0
      Ryujinx.Graphics.GAL/Color/ColorSI.cs
  14. 18 0
      Ryujinx.Graphics.GAL/Color/ColorUI.cs
  15. 14 0
      Ryujinx.Graphics.GAL/CompareOp.cs
  16. 9 0
      Ryujinx.Graphics.GAL/CounterType.cs
  17. 47 0
      Ryujinx.Graphics.GAL/DepthStencil/DepthStencilState.cs
  18. 20 0
      Ryujinx.Graphics.GAL/DepthStencil/DepthTestDescriptor.cs
  19. 14 0
      Ryujinx.Graphics.GAL/DepthStencil/StencilOp.cs
  20. 56 0
      Ryujinx.Graphics.GAL/DepthStencil/StencilTestDescriptor.cs
  21. 18 0
      Ryujinx.Graphics.GAL/Extents2D.cs
  22. 3 3
      Ryujinx.Graphics.GAL/Face.cs
  23. 218 0
      Ryujinx.Graphics.GAL/Format.cs
  24. 8 0
      Ryujinx.Graphics.GAL/FrontFace.cs
  25. 15 0
      Ryujinx.Graphics.GAL/IBuffer.cs
  26. 12 0
      Ryujinx.Graphics.GAL/IComputePipeline.cs
  27. 69 0
      Ryujinx.Graphics.GAL/IGraphicsPipeline.cs
  28. 6 0
      Ryujinx.Graphics.GAL/IProgram.cs
  29. 33 0
      Ryujinx.Graphics.GAL/IRenderer.cs
  30. 6 0
      Ryujinx.Graphics.GAL/ISampler.cs
  31. 6 0
      Ryujinx.Graphics.GAL/IShader.cs
  32. 21 0
      Ryujinx.Graphics.GAL/ITexture.cs
  33. 13 0
      Ryujinx.Graphics.GAL/IWindow.cs
  34. 28 0
      Ryujinx.Graphics.GAL/ImageCrop.cs
  35. 9 0
      Ryujinx.Graphics.GAL/IndexType.cs
  36. 17 0
      Ryujinx.Graphics.GAL/InputAssembler/VertexAttribDescriptor.cs
  37. 17 0
      Ryujinx.Graphics.GAL/InputAssembler/VertexBufferDescriptor.cs
  38. 12 0
      Ryujinx.Graphics.GAL/PolygonModeMask.cs
  39. 21 0
      Ryujinx.Graphics.GAL/PrimitiveTopology.cs
  40. 18 0
      Ryujinx.Graphics.GAL/RectangleF.cs
  41. 12 0
      Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj
  42. 14 0
      Ryujinx.Graphics.GAL/Sampler/AddressMode.cs
  43. 8 0
      Ryujinx.Graphics.GAL/Sampler/CompareMode.cs
  44. 8 0
      Ryujinx.Graphics.GAL/Sampler/MagFilter.cs
  45. 12 0
      Ryujinx.Graphics.GAL/Sampler/MinFilter.cs
  46. 52 0
      Ryujinx.Graphics.GAL/Sampler/SamplerCreateInfo.cs
  47. 8 0
      Ryujinx.Graphics.GAL/Texture/DepthStencilMode.cs
  48. 12 0
      Ryujinx.Graphics.GAL/Texture/SwizzleComponent.cs
  49. 17 0
      Ryujinx.Graphics.GAL/Texture/Target.cs
  50. 115 0
      Ryujinx.Graphics.GAL/Texture/TextureCreateInfo.cs
  51. 4 0
      Ryujinx.Graphics.GAL/TextureReleaseCallback.cs
  52. 33 0
      Ryujinx.Graphics.GAL/Viewport.cs
  53. 14 0
      Ryujinx.Graphics.GAL/ViewportSwizzle.cs
  54. 11 0
      Ryujinx.Graphics.Gpu/ClassId.cs
  55. 14 0
      Ryujinx.Graphics.Gpu/Constants.cs
  56. 25 0
      Ryujinx.Graphics.Gpu/Debugging.cs
  57. 16 25
      Ryujinx.Graphics.Gpu/DmaPusher.cs
  58. 83 0
      Ryujinx.Graphics.Gpu/Engine/Compute.cs
  59. 126 0
      Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs
  60. 18 0
      Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs
  61. 17 0
      Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs
  62. 42 0
      Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs
  63. 55 0
      Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
  64. 79 0
      Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs
  65. 70 0
      Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
  66. 133 0
      Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs
  67. 100 0
      Ryujinx.Graphics.Gpu/Engine/MethodReport.cs
  68. 26 0
      Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs
  69. 52 0
      Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs
  70. 18 0
      Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs
  71. 784 0
      Ryujinx.Graphics.Gpu/Engine/Methods.cs
  72. 34 0
      Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs
  73. 228 0
      Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs
  74. 38 44
      Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs
  75. 100 0
      Ryujinx.Graphics.Gpu/GpuContext.cs
  76. 12 0
      Ryujinx.Graphics.Gpu/GraphicsConfig.cs
  77. 62 0
      Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs
  78. 31 0
      Ryujinx.Graphics.Gpu/Image/FormatInfo.cs
  79. 201 0
      Ryujinx.Graphics.Gpu/Image/FormatTable.cs
  80. 99 0
      Ryujinx.Graphics.Gpu/Image/Pool.cs
  81. 9 0
      Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs
  82. 52 0
      Ryujinx.Graphics.Gpu/Image/Sampler.cs
  83. 132 0
      Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs
  84. 61 0
      Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
  85. 719 0
      Ryujinx.Graphics.Gpu/Image/Texture.cs
  86. 17 0
      Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs
  87. 95 0
      Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
  88. 35 0
      Ryujinx.Graphics.Gpu/Image/TextureComponent.cs
  89. 119 0
      Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs
  90. 11 0
      Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs
  91. 101 0
      Ryujinx.Graphics.Gpu/Image/TextureInfo.cs
  92. 669 0
      Ryujinx.Graphics.Gpu/Image/TextureManager.cs
  93. 53 0
      Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs
  94. 219 0
      Ryujinx.Graphics.Gpu/Image/TexturePool.cs
  95. 73 0
      Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs
  96. 13 0
      Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs
  97. 49 0
      Ryujinx.Graphics.Gpu/Image/TextureTarget.cs
  98. 19 20
      Ryujinx.Graphics.Gpu/MacroInterpreter.cs
  99. 99 0
      Ryujinx.Graphics.Gpu/Memory/Buffer.cs
  100. 8 0
      Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs

+ 2 - 5
ARMeilleure/Instructions/InstEmitMemoryHelper.cs

@@ -355,12 +355,9 @@ namespace ARMeilleure.Instructions
             }
             while (bit < context.Memory.AddressSpaceBits);
 
-            if (!context.Memory.HasWriteWatchSupport)
-            {
-                Operand hasFlagSet = context.BitwiseAnd(pte, Const((long)MemoryManager.PteFlagsMask));
+            Operand hasFlagSet = context.BitwiseAnd(pte, Const((long)MemoryManager.PteFlagsMask));
 
-                context.BranchIfTrue(lblFallbackPath, hasFlagSet);
-            }
+            context.BranchIfTrue(lblFallbackPath, hasFlagSet);
 
             Operand pageOffset = context.BitwiseAnd(address, Const(address.Type, MemoryManager.PageMask));
 

+ 0 - 37
ARMeilleure/Memory/IMemory.cs

@@ -1,37 +0,0 @@
-namespace ARMeilleure.Memory
-{
-    public interface IMemory
-    {
-        sbyte ReadSByte(long position);
-
-        short ReadInt16(long position);
-
-        int ReadInt32(long position);
-
-        long ReadInt64(long position);
-
-        byte ReadByte(long position);
-
-        ushort ReadUInt16(long position);
-
-        uint ReadUInt32(long position);
-
-        ulong ReadUInt64(long position);
-
-        void WriteSByte(long position, sbyte value);
-
-        void WriteInt16(long position, short value);
-
-        void WriteInt32(long position, int value);
-
-        void WriteInt64(long position, long value);
-
-        void WriteByte(long position, byte value);
-
-        void WriteUInt16(long position, ushort value);
-
-        void WriteUInt32(long position, uint value);
-
-        void WriteUInt64(long position, ulong value);
-    }
-}

+ 0 - 24
ARMeilleure/Memory/MemoryManagement.cs

@@ -6,8 +6,6 @@ namespace ARMeilleure.Memory
 {
     public static class MemoryManagement
     {
-        public static bool HasWriteWatchSupport => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
-
         public static IntPtr Allocate(ulong size)
         {
             if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -88,27 +86,5 @@ namespace ARMeilleure.Memory
                 throw new PlatformNotSupportedException();
             }
         }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static bool GetModifiedPages(
-            IntPtr    address,
-            IntPtr    size,
-            IntPtr[]  addresses,
-            out ulong count)
-        {
-            // This is only supported on windows, but returning
-            // false (failed) is also valid for platforms without
-            // write tracking support on the OS.
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
-            {
-                return MemoryManagementWindows.GetModifiedPages(address, size, addresses, out count);
-            }
-            else
-            {
-                count = 0;
-
-                return false;
-            }
-        }
     }
 }

+ 0 - 37
ARMeilleure/Memory/MemoryManagementWindows.cs

@@ -36,12 +36,6 @@ namespace ARMeilleure.Memory
             WriteCombineModifierflag = 0x400
         }
 
-        private enum WriteWatchFlags : uint
-        {
-            None  = 0,
-            Reset = 1
-        }
-
         [DllImport("kernel32.dll")]
         private static extern IntPtr VirtualAlloc(
             IntPtr           lpAddress,
@@ -62,15 +56,6 @@ namespace ARMeilleure.Memory
             IntPtr         dwSize,
             AllocationType dwFreeType);
 
-        [DllImport("kernel32.dll")]
-        private static extern int GetWriteWatch(
-            WriteWatchFlags dwFlags,
-            IntPtr          lpBaseAddress,
-            IntPtr          dwRegionSize,
-            IntPtr[]        lpAddresses,
-            ref ulong       lpdwCount,
-            out uint        lpdwGranularity);
-
         public static IntPtr Allocate(IntPtr size)
         {
             const AllocationType flags =
@@ -130,27 +115,5 @@ namespace ARMeilleure.Memory
         {
             return VirtualFree(address, IntPtr.Zero, AllocationType.Release);
         }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public static bool GetModifiedPages(
-            IntPtr    address,
-            IntPtr    size,
-            IntPtr[]  addresses,
-            out ulong count)
-        {
-            ulong pagesCount = (ulong)addresses.Length;
-
-            int result = GetWriteWatch(
-                WriteWatchFlags.Reset,
-                address,
-                size,
-                addresses,
-                ref pagesCount,
-                out uint granularity);
-
-            count = pagesCount;
-
-            return result == 0;
-        }
     }
 }

+ 28 - 173
ARMeilleure/Memory/MemoryManager.cs

@@ -1,5 +1,6 @@
 using ARMeilleure.State;
 using System;
+using System.Collections.Generic;
 using System.Runtime.InteropServices;
 using System.Threading;
 
@@ -29,8 +30,6 @@ namespace ARMeilleure.Memory
         internal int PtLevelSize { get; }
         internal int PtLevelMask { get; }
 
-        public bool HasWriteWatchSupport => MemoryManagement.HasWriteWatchSupport;
-
         public int  AddressSpaceBits { get; }
         public long AddressSpaceSize { get; }
 
@@ -254,119 +253,57 @@ namespace ARMeilleure.Memory
             return ptePtr;
         }
 
-        public bool IsRegionModified(long position, long size)
+        public unsafe (ulong, ulong)[] GetModifiedRanges(ulong address, ulong size)
         {
-            if (!HasWriteWatchSupport)
-            {
-                return IsRegionModifiedFallback(position, size);
-            }
-
-            IntPtr address = Translate(position);
-
-            IntPtr baseAddr     = address;
-            IntPtr expectedAddr = address;
-
-            long pendingPages = 0;
-
-            long pages = size / PageSize;
-
-            bool modified = false;
-
-            bool IsAnyPageModified()
-            {
-                IntPtr pendingSize = new IntPtr(pendingPages * PageSize);
-
-                IntPtr[] addresses = new IntPtr[pendingPages];
-
-                bool result = GetModifiedPages(baseAddr, pendingSize, addresses, out ulong count);
-
-                if (result)
-                {
-                    return count != 0;
-                }
-                else
-                {
-                    return true;
-                }
-            }
-
-            while (pages-- > 0)
-            {
-                if (address != expectedAddr)
-                {
-                    modified |= IsAnyPageModified();
-
-                    baseAddr = address;
-
-                    pendingPages = 0;
-                }
-
-                expectedAddr = address + PageSize;
-
-                pendingPages++;
-
-                if (pages == 0)
-                {
-                    break;
-                }
+            List<(ulong, ulong)> ranges = new List<(ulong, ulong)>();
 
-                position += PageSize;
-
-                address = Translate(position);
-            }
+            ulong endAddress = (address + size + PageMask) & ~(ulong)PageMask;
 
-            if (pendingPages != 0)
-            {
-                modified |= IsAnyPageModified();
-            }
+            address &= ~(ulong)PageMask;
 
-            return modified;
-        }
+            ulong currAddr = address;
+            ulong currSize = 0;
 
-        private unsafe bool IsRegionModifiedFallback(long position, long size)
-        {
-            long endAddr = (position + size + PageMask) & ~PageMask;
-
-            bool modified = false;
-
-            while ((ulong)position < (ulong)endAddr)
+            while (address < endAddress)
             {
-                if (IsValidPosition(position))
+                if (IsValidPosition((long)address))
                 {
-                    byte* ptr = ((byte**)_pageTable)[position >> PageBits];
+                    byte* ptr = ((byte**)_pageTable)[address >> PageBits];
 
                     ulong ptrUlong = (ulong)ptr;
 
                     if ((ptrUlong & PteFlagNotModified) == 0)
                     {
-                        modified = true;
+                        // Modified.
+                        currSize += PageSize;
+
+                        SetPtEntryFlag((long)address, PteFlagNotModified);
+                    }
+                    else
+                    {
+                        if (currSize != 0)
+                        {
+                            ranges.Add((currAddr, currSize));
+                        }
 
-                        SetPtEntryFlag(position, PteFlagNotModified);
+                        currAddr = address + PageSize;
+                        currSize = 0;
                     }
                 }
                 else
                 {
-                    modified = true;
+                    currSize += PageSize;
                 }
 
-                position += PageSize;
+                address += PageSize;
             }
 
-            return modified;
-        }
-
-        public bool TryGetHostAddress(long position, long size, out IntPtr ptr)
-        {
-            if (IsContiguous(position, size))
+            if (currSize != 0)
             {
-                ptr = (IntPtr)Translate(position);
-
-                return true;
+                ranges.Add((currAddr, currSize));
             }
 
-            ptr = IntPtr.Zero;
-
-            return false;
+            return ranges.ToArray();
         }
 
         private bool IsContiguous(long position, long size)
@@ -612,41 +549,6 @@ namespace ARMeilleure.Memory
             return data;
         }
 
-        public void ReadBytes(long position, byte[] data, int startIndex, int size)
-        {
-            // Note: This will be moved later.
-            long endAddr = position + size;
-
-            if ((ulong)size > int.MaxValue)
-            {
-                throw new ArgumentOutOfRangeException(nameof(size));
-            }
-
-            if ((ulong)endAddr < (ulong)position)
-            {
-                throw new ArgumentOutOfRangeException(nameof(position));
-            }
-
-            int offset = startIndex;
-
-            while ((ulong)position < (ulong)endAddr)
-            {
-                long pageLimit = (position + PageSize) & ~(long)PageMask;
-
-                if ((ulong)pageLimit > (ulong)endAddr)
-                {
-                    pageLimit = endAddr;
-                }
-
-                int copySize = (int)(pageLimit - position);
-
-                Marshal.Copy(Translate(position), data, offset, copySize);
-
-                position += copySize;
-                offset   += copySize;
-            }
-        }
-
         public void WriteSByte(long position, sbyte value)
         {
             WriteByte(position, (byte)value);
@@ -746,53 +648,6 @@ namespace ARMeilleure.Memory
             }
         }
 
-        public void WriteBytes(long position, byte[] data, int startIndex, int size)
-        {
-            // Note: This will be moved later.
-            long endAddr = position + size;
-
-            if ((ulong)endAddr < (ulong)position)
-            {
-                throw new ArgumentOutOfRangeException(nameof(position));
-            }
-
-            int offset = startIndex;
-
-            while ((ulong)position < (ulong)endAddr)
-            {
-                long pageLimit = (position + PageSize) & ~(long)PageMask;
-
-                if ((ulong)pageLimit > (ulong)endAddr)
-                {
-                    pageLimit = endAddr;
-                }
-
-                int copySize = (int)(pageLimit - position);
-
-                Marshal.Copy(data, offset, Translate(position), copySize);
-
-                position += copySize;
-                offset   += copySize;
-            }
-        }
-
-        public void CopyBytes(long src, long dst, long size)
-        {
-            // Note: This will be moved later.
-            if (IsContiguous(src, size) &&
-                IsContiguous(dst, size))
-            {
-                byte* srcPtr = (byte*)Translate(src);
-                byte* dstPtr = (byte*)Translate(dst);
-
-                Buffer.MemoryCopy(srcPtr, dstPtr, size, size);
-            }
-            else
-            {
-                WriteBytes(dst, ReadBytes(src, size));
-            }
-        }
-
         public void Dispose()
         {
             Dispose(true);

+ 19 - 2
Ryujinx.Common/PerformanceCounter.cs

@@ -1,9 +1,11 @@
-using System.Diagnostics;
+using System.Diagnostics;
 
 namespace Ryujinx.Common
 {
     public static class PerformanceCounter
     {
+        private static double _ticksToNs;
+
         /// <summary>
         /// Represents the number of ticks in 1 day.
         /// </summary>
@@ -53,6 +55,19 @@ namespace Ryujinx.Common
             }
         }
 
+        /// <summary>
+        /// Gets the number of nanoseconds elapsed since the system started.
+        /// </summary>
+        public static long ElapsedNanoseconds
+        {
+            get
+            {
+                long timestamp = Stopwatch.GetTimestamp();
+
+                return (long)(timestamp * _ticksToNs);
+            }
+        }
+
         static PerformanceCounter()
         {
             TicksPerMillisecond = Stopwatch.Frequency / 1000;
@@ -60,6 +75,8 @@ namespace Ryujinx.Common
             TicksPerMinute      = TicksPerSecond * 60;
             TicksPerHour        = TicksPerMinute * 60;
             TicksPerDay         = TicksPerHour * 24;
+
+            _ticksToNs = 1000000000.0 / (double)Stopwatch.Frequency;
         }
     }
-}
+}

+ 32 - 0
Ryujinx.Graphics.GAL/Blend/BlendDescriptor.cs

@@ -0,0 +1,32 @@
+namespace Ryujinx.Graphics.GAL.Blend
+{
+    public struct BlendDescriptor
+    {
+        public bool Enable { get; }
+
+        public BlendOp     ColorOp        { get; }
+        public BlendFactor ColorSrcFactor { get; }
+        public BlendFactor ColorDstFactor { get; }
+        public BlendOp     AlphaOp        { get; }
+        public BlendFactor AlphaSrcFactor { get; }
+        public BlendFactor AlphaDstFactor { get; }
+
+        public BlendDescriptor(
+            bool        enable,
+            BlendOp     colorOp,
+            BlendFactor colorSrcFactor,
+            BlendFactor colorDstFactor,
+            BlendOp     alphaOp,
+            BlendFactor alphaSrcFactor,
+            BlendFactor alphaDstFactor)
+        {
+            Enable         = enable;
+            ColorOp        = colorOp;
+            ColorSrcFactor = colorSrcFactor;
+            ColorDstFactor = colorDstFactor;
+            AlphaOp        = alphaOp;
+            AlphaSrcFactor = alphaSrcFactor;
+            AlphaDstFactor = alphaDstFactor;
+        }
+    }
+}

+ 25 - 0
Ryujinx.Graphics.GAL/Blend/BlendFactor.cs

@@ -0,0 +1,25 @@
+namespace Ryujinx.Graphics.GAL.Blend
+{
+    public enum BlendFactor
+    {
+        Zero = 1,
+        One,
+        SrcColor,
+        OneMinusSrcColor,
+        SrcAlpha,
+        OneMinusSrcAlpha,
+        DstAlpha,
+        OneMinusDstAlpha,
+        DstColor,
+        OneMinusDstColor,
+        SrcAlphaSaturate,
+        Src1Color = 0x10,
+        OneMinusSrc1Color,
+        Src1Alpha,
+        OneMinusSrc1Alpha,
+        ConstantColor = 0xc001,
+        OneMinusConstantColor,
+        ConstantAlpha,
+        OneMinusConstantAlpha
+    }
+}

+ 11 - 0
Ryujinx.Graphics.GAL/Blend/BlendOp.cs

@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.GAL.Blend
+{
+    public enum BlendOp
+    {
+        Add = 1,
+        Subtract,
+        ReverseSubtract,
+        Minimum,
+        Maximum
+    }
+}

+ 21 - 0
Ryujinx.Graphics.GAL/BufferRange.cs

@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct BufferRange
+    {
+        private static BufferRange _empty = new BufferRange(null, 0, 0);
+
+        public BufferRange Empty => _empty;
+
+        public IBuffer Buffer { get; }
+
+        public int Offset { get; }
+        public int Size   { get; }
+
+        public BufferRange(IBuffer buffer, int offset, int size)
+        {
+            Buffer = buffer;
+            Offset = offset;
+            Size   = size;
+        }
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/Capabilities.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct Capabilities
+    {
+        public bool SupportsAstcCompression { get; }
+
+        public Capabilities(bool supportsAstcCompression)
+        {
+            SupportsAstcCompression = supportsAstcCompression;
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.GAL/Color/ColorF.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Color
+{
+    public struct ColorF
+    {
+        public float Red   { get; }
+        public float Green { get; }
+        public float Blue  { get; }
+        public float Alpha { get; }
+
+        public ColorF(float red, float green, float blue, float alpha)
+        {
+            Red   = red;
+            Green = green;
+            Blue  = blue;
+            Alpha = alpha;
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.GAL/Color/ColorSI.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Color
+{
+    public struct ColorSI
+    {
+        public int Red   { get; }
+        public int Green { get; }
+        public int Blue  { get; }
+        public int Alpha { get; }
+
+        public ColorSI(int red, int green, int blue, int alpha)
+        {
+            Red   = red;
+            Green = green;
+            Blue  = blue;
+            Alpha = alpha;
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.GAL/Color/ColorUI.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL.Color
+{
+    public struct ColorUI
+    {
+        public uint Red   { get; }
+        public uint Green { get; }
+        public uint Blue  { get; }
+        public uint Alpha { get; }
+
+        public ColorUI(uint red, uint green, uint blue, uint alpha)
+        {
+            Red   = red;
+            Green = green;
+            Blue  = blue;
+            Alpha = alpha;
+        }
+    }
+}

+ 14 - 0
Ryujinx.Graphics.GAL/CompareOp.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum CompareOp
+    {
+        Never = 1,
+        Less,
+        Equal,
+        LessOrEqual,
+        Greater,
+        NotEqual,
+        GreaterOrEqual,
+        Always
+    }
+}

+ 9 - 0
Ryujinx.Graphics.GAL/CounterType.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum CounterType
+    {
+        SamplesPassed,
+        PrimitivesGenerated,
+        TransformFeedbackPrimitivesWritten
+    }
+}

+ 47 - 0
Ryujinx.Graphics.GAL/DepthStencil/DepthStencilState.cs

@@ -0,0 +1,47 @@
+namespace Ryujinx.Graphics.GAL.DepthStencil
+{
+    public struct DepthStencilState
+    {
+        public bool DepthTestEnable   { get; }
+        public bool DepthWriteEnable  { get; }
+        public bool StencilTestEnable { get; }
+
+        public CompareOp DepthFunc          { get; }
+        public CompareOp StencilFrontFunc   { get; }
+        public StencilOp StencilFrontSFail  { get; }
+        public StencilOp StencilFrontDpPass { get; }
+        public StencilOp StencilFrontDpFail { get; }
+        public CompareOp StencilBackFunc    { get; }
+        public StencilOp StencilBackSFail   { get; }
+        public StencilOp StencilBackDpPass  { get; }
+        public StencilOp StencilBackDpFail  { get; }
+
+        public DepthStencilState(
+            bool      depthTestEnable,
+            bool      depthWriteEnable,
+            bool      stencilTestEnable,
+            CompareOp depthFunc,
+            CompareOp stencilFrontFunc,
+            StencilOp stencilFrontSFail,
+            StencilOp stencilFrontDpPass,
+            StencilOp stencilFrontDpFail,
+            CompareOp stencilBackFunc,
+            StencilOp stencilBackSFail,
+            StencilOp stencilBackDpPass,
+            StencilOp stencilBackDpFail)
+        {
+            DepthTestEnable    = depthTestEnable;
+            DepthWriteEnable   = depthWriteEnable;
+            StencilTestEnable  = stencilTestEnable;
+            DepthFunc          = depthFunc;
+            StencilFrontFunc   = stencilFrontFunc;
+            StencilFrontSFail  = stencilFrontSFail;
+            StencilFrontDpPass = stencilFrontDpPass;
+            StencilFrontDpFail = stencilFrontDpFail;
+            StencilBackFunc    = stencilBackFunc;
+            StencilBackSFail   = stencilBackSFail;
+            StencilBackDpPass  = stencilBackDpPass;
+            StencilBackDpFail  = stencilBackDpFail;
+        }
+    }
+}

+ 20 - 0
Ryujinx.Graphics.GAL/DepthStencil/DepthTestDescriptor.cs

@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.GAL.DepthStencil
+{
+    public struct DepthTestDescriptor
+    {
+        public bool TestEnable  { get; }
+        public bool WriteEnable { get; }
+
+        public CompareOp Func { get; }
+
+        public DepthTestDescriptor(
+            bool      testEnable,
+            bool      writeEnable,
+            CompareOp func)
+        {
+            TestEnable  = testEnable;
+            WriteEnable = writeEnable;
+            Func        = func;
+        }
+    }
+}

+ 14 - 0
Ryujinx.Graphics.GAL/DepthStencil/StencilOp.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL.DepthStencil
+{
+    public enum StencilOp
+    {
+        Keep = 1,
+        Zero,
+        Replace,
+        IncrementAndClamp,
+        DecrementAndClamp,
+        Invert,
+        IncrementAndWrap,
+        DecrementAndWrap
+    }
+}

+ 56 - 0
Ryujinx.Graphics.GAL/DepthStencil/StencilTestDescriptor.cs

@@ -0,0 +1,56 @@
+namespace Ryujinx.Graphics.GAL.DepthStencil
+{
+    public struct StencilTestDescriptor
+    {
+        public bool TestEnable { get; }
+
+        public CompareOp FrontFunc     { get; }
+        public StencilOp FrontSFail    { get; }
+        public StencilOp FrontDpPass   { get; }
+        public StencilOp FrontDpFail   { get; }
+        public int       FrontFuncRef  { get; }
+        public int       FrontFuncMask { get; }
+        public int       FrontMask     { get; }
+        public CompareOp BackFunc      { get; }
+        public StencilOp BackSFail     { get; }
+        public StencilOp BackDpPass    { get; }
+        public StencilOp BackDpFail    { get; }
+        public int       BackFuncRef   { get; }
+        public int       BackFuncMask  { get; }
+        public int       BackMask      { get; }
+
+        public StencilTestDescriptor(
+            bool      testEnable,
+            CompareOp frontFunc,
+            StencilOp frontSFail,
+            StencilOp frontDpPass,
+            StencilOp frontDpFail,
+            int       frontFuncRef,
+            int       frontFuncMask,
+            int       frontMask,
+            CompareOp backFunc,
+            StencilOp backSFail,
+            StencilOp backDpPass,
+            StencilOp backDpFail,
+            int       backFuncRef,
+            int       backFuncMask,
+            int       backMask)
+        {
+            TestEnable    = testEnable;
+            FrontFunc     = frontFunc;
+            FrontSFail    = frontSFail;
+            FrontDpPass   = frontDpPass;
+            FrontDpFail   = frontDpFail;
+            FrontFuncRef  = frontFuncRef;
+            FrontFuncMask = frontFuncMask;
+            FrontMask     = frontMask;
+            BackFunc      = backFunc;
+            BackSFail     = backSFail;
+            BackDpPass    = backDpPass;
+            BackDpFail    = backDpFail;
+            BackFuncRef   = backFuncRef;
+            BackFuncMask  = backFuncMask;
+            BackMask      = backMask;
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.GAL/Extents2D.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct Extents2D
+    {
+        public int X1 { get; }
+        public int Y1 { get; }
+        public int X2 { get; }
+        public int Y2 { get; }
+
+        public Extents2D(int x1, int y1, int x2, int y2)
+        {
+            X1 = x1;
+            Y1 = y1;
+            X2 = x2;
+            Y2 = y2;
+        }
+    }
+}

+ 3 - 3
Ryujinx.Graphics/Gal/GalCullFace.cs → Ryujinx.Graphics.GAL/Face.cs

@@ -1,9 +1,9 @@
-namespace Ryujinx.Graphics.Gal
+namespace Ryujinx.Graphics.GAL
 {
-    public enum GalCullFace
+    public enum Face
     {
         Front        = 0x404,
         Back         = 0x405,
         FrontAndBack = 0x408
     }
-}
+}

+ 218 - 0
Ryujinx.Graphics.GAL/Format.cs

@@ -0,0 +1,218 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum Format
+    {
+        R8Unorm,
+        R8Snorm,
+        R8Uint,
+        R8Sint,
+        R16Float,
+        R16Unorm,
+        R16Snorm,
+        R16Uint,
+        R16Sint,
+        R32Float,
+        R32Uint,
+        R32Sint,
+        R8G8Unorm,
+        R8G8Snorm,
+        R8G8Uint,
+        R8G8Sint,
+        R16G16Float,
+        R16G16Unorm,
+        R16G16Snorm,
+        R16G16Uint,
+        R16G16Sint,
+        R32G32Float,
+        R32G32Uint,
+        R32G32Sint,
+        R8G8B8Unorm,
+        R8G8B8Snorm,
+        R8G8B8Uint,
+        R8G8B8Sint,
+        R16G16B16Float,
+        R16G16B16Unorm,
+        R16G16B16Snorm,
+        R16G16B16Uint,
+        R16G16B16Sint,
+        R32G32B32Float,
+        R32G32B32Uint,
+        R32G32B32Sint,
+        R8G8B8A8Unorm,
+        R8G8B8A8Snorm,
+        R8G8B8A8Uint,
+        R8G8B8A8Sint,
+        R16G16B16A16Float,
+        R16G16B16A16Unorm,
+        R16G16B16A16Snorm,
+        R16G16B16A16Uint,
+        R16G16B16A16Sint,
+        R32G32B32A32Float,
+        R32G32B32A32Uint,
+        R32G32B32A32Sint,
+        S8Uint,
+        D16Unorm,
+        D24X8Unorm,
+        D32Float,
+        D24UnormS8Uint,
+        D32FloatS8Uint,
+        R8G8B8X8Srgb,
+        R8G8B8A8Srgb,
+        R4G4B4A4Unorm,
+        R5G5B5X1Unorm,
+        R5G5B5A1Unorm,
+        R5G6B5Unorm,
+        R10G10B10A2Unorm,
+        R10G10B10A2Uint,
+        R11G11B10Float,
+        R9G9B9E5Float,
+        Bc1RgbUnorm,
+        Bc1RgbaUnorm,
+        Bc2Unorm,
+        Bc3Unorm,
+        Bc1RgbSrgb,
+        Bc1RgbaSrgb,
+        Bc2Srgb,
+        Bc3Srgb,
+        Bc4Unorm,
+        Bc4Snorm,
+        Bc5Unorm,
+        Bc5Snorm,
+        Bc7Unorm,
+        Bc7Srgb,
+        Bc6HUfloat,
+        Bc6HSfloat,
+        R8Uscaled,
+        R8Sscaled,
+        R16Uscaled,
+        R16Sscaled,
+        R32Uscaled,
+        R32Sscaled,
+        R8G8Uscaled,
+        R8G8Sscaled,
+        R16G16Uscaled,
+        R16G16Sscaled,
+        R32G32Uscaled,
+        R32G32Sscaled,
+        R8G8B8Uscaled,
+        R8G8B8Sscaled,
+        R16G16B16Uscaled,
+        R16G16B16Sscaled,
+        R32G32B32Uscaled,
+        R32G32B32Sscaled,
+        R8G8B8A8Uscaled,
+        R8G8B8A8Sscaled,
+        R16G16B16A16Uscaled,
+        R16G16B16A16Sscaled,
+        R32G32B32A32Uscaled,
+        R32G32B32A32Sscaled,
+        R10G10B10A2Snorm,
+        R10G10B10A2Sint,
+        R10G10B10A2Uscaled,
+        R10G10B10A2Sscaled,
+        R8G8B8X8Unorm,
+        R8G8B8X8Snorm,
+        R8G8B8X8Uint,
+        R8G8B8X8Sint,
+        R16G16B16X16Float,
+        R16G16B16X16Unorm,
+        R16G16B16X16Snorm,
+        R16G16B16X16Uint,
+        R16G16B16X16Sint,
+        R32G32B32X32Float,
+        R32G32B32X32Uint,
+        R32G32B32X32Sint,
+        Astc4x4Unorm,
+        Astc5x4Unorm,
+        Astc5x5Unorm,
+        Astc6x5Unorm,
+        Astc6x6Unorm,
+        Astc8x5Unorm,
+        Astc8x6Unorm,
+        Astc8x8Unorm,
+        Astc10x5Unorm,
+        Astc10x6Unorm,
+        Astc10x8Unorm,
+        Astc10x10Unorm,
+        Astc12x10Unorm,
+        Astc12x12Unorm,
+        Astc4x4Srgb,
+        Astc5x4Srgb,
+        Astc5x5Srgb,
+        Astc6x5Srgb,
+        Astc6x6Srgb,
+        Astc8x5Srgb,
+        Astc8x6Srgb,
+        Astc8x8Srgb,
+        Astc10x5Srgb,
+        Astc10x6Srgb,
+        Astc10x8Srgb,
+        Astc10x10Srgb,
+        Astc12x10Srgb,
+        Astc12x12Srgb,
+        B5G6R5Unorm,
+        B5G5R5X1Unorm,
+        B5G5R5A1Unorm,
+        A1B5G5R5Unorm,
+        B8G8R8X8Unorm,
+        B8G8R8A8Unorm,
+        B8G8R8X8Srgb,
+        B8G8R8A8Srgb
+    }
+
+    public static class FormatExtensions
+    {
+        public static bool IsAstc(this Format format)
+        {
+            return format.IsAstcUnorm() || format.IsAstcSrgb();
+        }
+
+        public static bool IsAstcUnorm(this Format format)
+        {
+            switch (format)
+            {
+                case Format.Astc4x4Unorm:
+                case Format.Astc5x4Unorm:
+                case Format.Astc5x5Unorm:
+                case Format.Astc6x5Unorm:
+                case Format.Astc6x6Unorm:
+                case Format.Astc8x5Unorm:
+                case Format.Astc8x6Unorm:
+                case Format.Astc8x8Unorm:
+                case Format.Astc10x5Unorm:
+                case Format.Astc10x6Unorm:
+                case Format.Astc10x8Unorm:
+                case Format.Astc10x10Unorm:
+                case Format.Astc12x10Unorm:
+                case Format.Astc12x12Unorm:
+                    return true;
+            }
+
+            return false;
+        }
+
+        public static bool IsAstcSrgb(this Format format)
+        {
+            switch (format)
+            {
+                case Format.Astc4x4Srgb:
+                case Format.Astc5x4Srgb:
+                case Format.Astc5x5Srgb:
+                case Format.Astc6x5Srgb:
+                case Format.Astc6x6Srgb:
+                case Format.Astc8x5Srgb:
+                case Format.Astc8x6Srgb:
+                case Format.Astc8x8Srgb:
+                case Format.Astc10x5Srgb:
+                case Format.Astc10x6Srgb:
+                case Format.Astc10x8Srgb:
+                case Format.Astc10x10Srgb:
+                case Format.Astc12x10Srgb:
+                case Format.Astc12x12Srgb:
+                    return true;
+            }
+
+            return false;
+        }
+    }
+}

+ 8 - 0
Ryujinx.Graphics.GAL/FrontFace.cs

@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum FrontFace
+    {
+        Clockwise        = 0x900,
+        CounterClockwise = 0x901
+    }
+}

+ 15 - 0
Ryujinx.Graphics.GAL/IBuffer.cs

@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IBuffer : IDisposable
+    {
+        void CopyTo(IBuffer destination, int srcOffset, int dstOffset, int size);
+
+        byte[] GetData(int offset, int size);
+
+        void SetData(Span<byte> data);
+
+        void SetData(int offset, Span<byte> data);
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/IComputePipeline.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IComputePipeline
+    {
+        void Dispatch(int groupsX, int groupsY, int groupsZ);
+
+        void SetProgram(IProgram program);
+
+        void SetStorageBuffer(int index, BufferRange buffer);
+        void SetUniformBuffer(int index, BufferRange buffer);
+    }
+}

+ 69 - 0
Ryujinx.Graphics.GAL/IGraphicsPipeline.cs

@@ -0,0 +1,69 @@
+using Ryujinx.Graphics.GAL.Blend;
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.GAL.DepthStencil;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IGraphicsPipeline
+    {
+        void BindBlendState(int index, BlendDescriptor blend);
+
+        void BindIndexBuffer(BufferRange buffer, IndexType type);
+
+        void BindProgram(IProgram program);
+
+        void BindSampler(int index, ShaderStage stage, ISampler sampler);
+        void BindTexture(int index, ShaderStage stage, ITexture texture);
+
+        void BindStorageBuffers(int index, ShaderStage stage, BufferRange[] buffers);
+        void BindUniformBuffers(int index, ShaderStage stage, BufferRange[] buffers);
+
+        void BindVertexAttribs(VertexAttribDescriptor[] vertexAttribs);
+        void BindVertexBuffers(VertexBufferDescriptor[] vertexBuffers);
+
+        void ClearRenderTargetColor(int index, uint componentMask, ColorF color);
+        void ClearRenderTargetColor(int index, uint componentMask, ColorSI color);
+        void ClearRenderTargetColor(int index, uint componentMask, ColorUI color);
+
+        void ClearRenderTargetDepthStencil(
+            float depthValue,
+            bool  depthMask,
+            int   stencilValue,
+            int   stencilMask);
+
+        void Draw(int vertexCount, int instanceCount, int firstVertex, int firstInstance);
+        void DrawIndexed(
+            int indexCount,
+            int instanceCount,
+            int firstIndex,
+            int firstVertex,
+            int firstInstance);
+        void DrawIndirect       (BufferRange buffer, ulong offset, int drawCount, int stride);
+        void DrawIndexedIndirect(BufferRange buffer, ulong offset, int drawCount, int stride);
+
+        void SetBlendColor(ColorF color);
+
+        void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
+
+        void SetDepthTest(DepthTestDescriptor depthTest);
+
+        void SetFaceCulling(bool enable, Face face);
+
+        void SetFrontFace(FrontFace frontFace);
+
+        void SetPrimitiveRestart(bool enable, int index);
+
+        void SetPrimitiveTopology(PrimitiveTopology topology);
+
+        void SetRenderTargetColorMasks(uint[] componentMask);
+
+        void SetRenderTargets(ITexture color3D, ITexture depthStencil);
+        void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
+
+        void SetStencilTest(StencilTestDescriptor stencilTest);
+
+        void SetViewports(int first, Viewport[] viewports);
+    }
+}

+ 6 - 0
Ryujinx.Graphics.GAL/IProgram.cs

@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IProgram : IDisposable { }
+}

+ 33 - 0
Ryujinx.Graphics.GAL/IRenderer.cs

@@ -0,0 +1,33 @@
+using Ryujinx.Graphics.GAL.Sampler;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IRenderer
+    {
+        IComputePipeline  ComputePipeline  { get; }
+        IGraphicsPipeline GraphicsPipeline { get; }
+
+        IWindow Window { get; }
+
+        IShader CompileShader(ShaderProgram shader);
+
+        IBuffer CreateBuffer(int size);
+
+        IProgram CreateProgram(IShader[] shaders);
+
+        ISampler CreateSampler(SamplerCreateInfo info);
+        ITexture CreateTexture(TextureCreateInfo info);
+
+        void FlushPipelines();
+
+        Capabilities GetCapabilities();
+
+        ulong GetCounter(CounterType type);
+
+        void InitializeCounters();
+
+        void ResetCounter(CounterType type);
+    }
+}

+ 6 - 0
Ryujinx.Graphics.GAL/ISampler.cs

@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface ISampler : IDisposable { }
+}

+ 6 - 0
Ryujinx.Graphics.GAL/IShader.cs

@@ -0,0 +1,6 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IShader : IDisposable { }
+}

+ 21 - 0
Ryujinx.Graphics.GAL/ITexture.cs

@@ -0,0 +1,21 @@
+using Ryujinx.Graphics.GAL.Texture;
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface ITexture : IDisposable
+    {
+        int Handle { get; }
+
+        void CopyTo(ITexture destination);
+        void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
+
+        ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
+
+        int GetStorageDebugId();
+
+        byte[] GetData(int face);
+
+        void SetData(Span<byte> data);
+    }
+}

+ 13 - 0
Ryujinx.Graphics.GAL/IWindow.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    public interface IWindow
+    {
+        void Present();
+
+        void QueueTexture(ITexture texture, ImageCrop crop, object context);
+
+        void RegisterTextureReleaseCallback(TextureReleaseCallback callback);
+    }
+}

+ 28 - 0
Ryujinx.Graphics.GAL/ImageCrop.cs

@@ -0,0 +1,28 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct ImageCrop
+    {
+        public int  Left   { get; }
+        public int  Right  { get; }
+        public int  Top    { get; }
+        public int  Bottom { get; }
+        public bool FlipX  { get; }
+        public bool FlipY  { get; }
+
+        public ImageCrop(
+            int  left,
+            int  right,
+            int  top,
+            int  bottom,
+            bool flipX,
+            bool flipY)
+        {
+            Left   = left;
+            Right  = right;
+            Top    = top;
+            Bottom = bottom;
+            FlipX  = flipX;
+            FlipY  = flipY;
+        }
+    }
+}

+ 9 - 0
Ryujinx.Graphics.GAL/IndexType.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum IndexType
+    {
+        UByte,
+        UShort,
+        UInt
+    }
+}

+ 17 - 0
Ryujinx.Graphics.GAL/InputAssembler/VertexAttribDescriptor.cs

@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL.InputAssembler
+{
+    public struct VertexAttribDescriptor
+    {
+        public int BufferIndex  { get; }
+        public int Offset       { get; }
+
+        public Format Format { get; }
+
+        public VertexAttribDescriptor(int bufferIndex, int offset, Format format)
+        {
+            BufferIndex = bufferIndex;
+            Offset      = offset;
+            Format      = format;
+        }
+    }
+}

+ 17 - 0
Ryujinx.Graphics.GAL/InputAssembler/VertexBufferDescriptor.cs

@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL.InputAssembler
+{
+    public struct VertexBufferDescriptor
+    {
+        public BufferRange Buffer { get; }
+
+        public int Stride  { get; }
+        public int Divisor { get; }
+
+        public VertexBufferDescriptor(BufferRange buffer, int stride, int divisor)
+        {
+            Buffer  = buffer;
+            Stride  = stride;
+            Divisor = divisor;
+        }
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/PolygonModeMask.cs

@@ -0,0 +1,12 @@
+using System;
+
+namespace Ryujinx.Graphics.GAL
+{
+    [Flags]
+    public enum PolygonModeMask
+    {
+        Point = 1 << 0,
+        Line  = 1 << 1,
+        Fill  = 1 << 2
+    }
+}

+ 21 - 0
Ryujinx.Graphics.GAL/PrimitiveTopology.cs

@@ -0,0 +1,21 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum PrimitiveTopology
+    {
+        Points,
+        Lines,
+        LineLoop,
+        LineStrip,
+        Triangles,
+        TriangleStrip,
+        TriangleFan,
+        Quads,
+        QuadStrip,
+        Polygon,
+        LinesAdjacency,
+        LineStripAdjacency,
+        TrianglesAdjacency,
+        TriangleStripAdjacency,
+        Patches
+    }
+}

+ 18 - 0
Ryujinx.Graphics.GAL/RectangleF.cs

@@ -0,0 +1,18 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct RectangleF
+    {
+        public float X      { get; }
+        public float Y      { get; }
+        public float Width  { get; }
+        public float Height { get; }
+
+        public RectangleF(float x, float y, float width, float height)
+        {
+            X      = x;
+            Y      = y;
+            Width  = width;
+            Height = height;
+        }
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/Ryujinx.Graphics.GAL.csproj

@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <ItemGroup>
+    <ProjectReference Include="..\Ryujinx.Graphics.Shader\Ryujinx.Graphics.Shader.csproj" />
+    <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
+  </ItemGroup>
+
+  <PropertyGroup>
+    <TargetFramework>netcoreapp3.0</TargetFramework>
+  </PropertyGroup>
+
+</Project>

+ 14 - 0
Ryujinx.Graphics.GAL/Sampler/AddressMode.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL.Sampler
+{
+    public enum AddressMode
+    {
+        Repeat,
+        MirroredRepeat,
+        ClampToEdge,
+        ClampToBorder,
+        Clamp,
+        MirrorClampToEdge,
+        MirrorClampToBorder,
+        MirrorClamp
+    }
+}

+ 8 - 0
Ryujinx.Graphics.GAL/Sampler/CompareMode.cs

@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL.Sampler
+{
+    public enum CompareMode
+    {
+        None,
+        CompareRToTexture
+    }
+}

+ 8 - 0
Ryujinx.Graphics.GAL/Sampler/MagFilter.cs

@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL.Sampler
+{
+    public enum MagFilter
+    {
+        Nearest = 1,
+        Linear
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/Sampler/MinFilter.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Sampler
+{
+    public enum MinFilter
+    {
+        Nearest = 1,
+        Linear,
+        NearestMipmapNearest,
+        LinearMipmapNearest,
+        NearestMipmapLinear,
+        LinearMipmapLinear
+    }
+}

+ 52 - 0
Ryujinx.Graphics.GAL/Sampler/SamplerCreateInfo.cs

@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.GAL.Color;
+
+namespace Ryujinx.Graphics.GAL.Sampler
+{
+    public struct SamplerCreateInfo
+    {
+        public MinFilter MinFilter { get; }
+        public MagFilter MagFilter { get; }
+
+        public AddressMode AddressU { get; }
+        public AddressMode AddressV { get; }
+        public AddressMode AddressP { get; }
+
+        public CompareMode CompareMode { get; }
+        public CompareOp   CompareOp   { get; }
+
+        public ColorF BorderColor { get; }
+
+        public float MinLod        { get; }
+        public float MaxLod        { get; }
+        public float MipLodBias    { get; }
+        public float MaxAnisotropy { get; }
+
+        public SamplerCreateInfo(
+            MinFilter   minFilter,
+            MagFilter   magFilter,
+            AddressMode addressU,
+            AddressMode addressV,
+            AddressMode addressP,
+            CompareMode compareMode,
+            CompareOp   compareOp,
+            ColorF      borderColor,
+            float       minLod,
+            float       maxLod,
+            float       mipLodBias,
+            float       maxAnisotropy)
+        {
+            MinFilter     = minFilter;
+            MagFilter     = magFilter;
+            AddressU      = addressU;
+            AddressV      = addressV;
+            AddressP      = addressP;
+            CompareMode   = compareMode;
+            CompareOp     = compareOp;
+            BorderColor   = borderColor;
+            MinLod        = minLod;
+            MaxLod        = maxLod;
+            MipLodBias    = mipLodBias;
+            MaxAnisotropy = maxAnisotropy;
+        }
+    }
+}

+ 8 - 0
Ryujinx.Graphics.GAL/Texture/DepthStencilMode.cs

@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.GAL.Texture
+{
+    public enum DepthStencilMode
+    {
+        Depth,
+        Stencil
+    }
+}

+ 12 - 0
Ryujinx.Graphics.GAL/Texture/SwizzleComponent.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.GAL.Texture
+{
+    public enum SwizzleComponent
+    {
+        Zero,
+        One,
+        Red,
+        Green,
+        Blue,
+        Alpha
+    }
+}

+ 17 - 0
Ryujinx.Graphics.GAL/Texture/Target.cs

@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.GAL.Texture
+{
+    public enum Target
+    {
+        Texture1D,
+        Texture2D,
+        Texture3D,
+        Texture1DArray,
+        Texture2DArray,
+        Texture2DMultisample,
+        Texture2DMultisampleArray,
+        Rectangle,
+        Cubemap,
+        CubemapArray,
+        TextureBuffer
+    }
+}

+ 115 - 0
Ryujinx.Graphics.GAL/Texture/TextureCreateInfo.cs

@@ -0,0 +1,115 @@
+using Ryujinx.Common;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Texture
+{
+    public struct TextureCreateInfo
+    {
+        public int Width         { get; }
+        public int Height        { get; }
+        public int Depth         { get; }
+        public int Levels        { get; }
+        public int Samples       { get; }
+        public int BlockWidth    { get; }
+        public int BlockHeight   { get; }
+        public int BytesPerPixel { get; }
+
+        public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+        public Format Format { get; }
+
+        public DepthStencilMode DepthStencilMode { get; }
+
+        public Target Target { get; }
+
+        public SwizzleComponent SwizzleR { get; }
+        public SwizzleComponent SwizzleG { get; }
+        public SwizzleComponent SwizzleB { get; }
+        public SwizzleComponent SwizzleA { get; }
+
+        public TextureCreateInfo(
+            int              width,
+            int              height,
+            int              depth,
+            int              levels,
+            int              samples,
+            int              blockWidth,
+            int              blockHeight,
+            int              bytesPerPixel,
+            Format           format,
+            DepthStencilMode depthStencilMode,
+            Target           target,
+            SwizzleComponent swizzleR,
+            SwizzleComponent swizzleG,
+            SwizzleComponent swizzleB,
+            SwizzleComponent swizzleA)
+        {
+            Width            = width;
+            Height           = height;
+            Depth            = depth;
+            Levels           = levels;
+            Samples          = samples;
+            BlockWidth       = blockWidth;
+            BlockHeight      = blockHeight;
+            BytesPerPixel    = bytesPerPixel;
+            Format           = format;
+            DepthStencilMode = depthStencilMode;
+            Target           = target;
+            SwizzleR         = swizzleR;
+            SwizzleG         = swizzleG;
+            SwizzleB         = swizzleB;
+            SwizzleA         = swizzleA;
+        }
+
+        public int GetMipSize(int level)
+        {
+            return GetMipStride(level) * GetLevelHeight(level) * GetLevelDepth(level);
+        }
+
+        public int GetMipStride(int level)
+        {
+            return BitUtils.AlignUp(GetLevelWidth(level) * BytesPerPixel, 4);
+        }
+
+        private int GetLevelWidth(int level)
+        {
+            return BitUtils.DivRoundUp(GetLevelSize(Width, level), BlockWidth);
+        }
+
+        private int GetLevelHeight(int level)
+        {
+            return BitUtils.DivRoundUp(GetLevelSize(Height, level), BlockHeight);
+        }
+
+        private int GetLevelDepth(int level)
+        {
+            return Target == Target.Texture3D ? GetLevelSize(Depth, level) : GetLayers();
+        }
+
+        public int GetDepthOrLayers()
+        {
+            return Target == Target.Texture3D ? Depth : GetLayers();
+        }
+
+        public int GetLayers()
+        {
+            if (Target == Target.Texture2DArray ||
+                Target == Target.Texture2DMultisampleArray ||
+                Target == Target.CubemapArray)
+            {
+                return Depth;
+            }
+            else if (Target == Target.Cubemap)
+            {
+                return 6;
+            }
+
+            return 1;
+        }
+
+        private static int GetLevelSize(int size, int level)
+        {
+            return Math.Max(1, size >> level);
+        }
+    }
+}

+ 4 - 0
Ryujinx.Graphics.GAL/TextureReleaseCallback.cs

@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public delegate void TextureReleaseCallback(object context);
+}

+ 33 - 0
Ryujinx.Graphics.GAL/Viewport.cs

@@ -0,0 +1,33 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public struct Viewport
+    {
+        public RectangleF Region { get; }
+
+        public ViewportSwizzle SwizzleX { get; }
+        public ViewportSwizzle SwizzleY { get; }
+        public ViewportSwizzle SwizzleZ { get; }
+        public ViewportSwizzle SwizzleW { get; }
+
+        public float DepthNear { get; }
+        public float DepthFar  { get; }
+
+        public Viewport(
+            RectangleF      region,
+            ViewportSwizzle swizzleX,
+            ViewportSwizzle swizzleY,
+            ViewportSwizzle swizzleZ,
+            ViewportSwizzle swizzleW,
+            float           depthNear,
+            float           depthFar)
+        {
+            Region    = region;
+            SwizzleX  = swizzleX;
+            SwizzleY  = swizzleY;
+            SwizzleZ  = swizzleZ;
+            SwizzleW  = swizzleW;
+            DepthNear = depthNear;
+            DepthFar  = depthFar;
+        }
+    }
+}

+ 14 - 0
Ryujinx.Graphics.GAL/ViewportSwizzle.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.GAL
+{
+    public enum ViewportSwizzle
+    {
+        PositiveX,
+        NegativeX,
+        PositiveY,
+        NegativeY,
+        PositiveZ,
+        NegativeZ,
+        PositiveW,
+        NegativeW
+    }
+}

+ 11 - 0
Ryujinx.Graphics.Gpu/ClassId.cs

@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    enum ClassId
+    {
+        Engine2D            = 0x902d,
+        Engine3D            = 0xb197,
+        EngineCompute       = 0xb1c0,
+        EngineInline2Memory = 0xa140,
+        EngineDma           = 0xb0b5
+    }
+}

+ 14 - 0
Ryujinx.Graphics.Gpu/Constants.cs

@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    static class Constants
+    {
+        public const int TotalCpUniformBuffers = 8;
+        public const int TotalCpStorageBuffers = 16;
+        public const int TotalGpUniformBuffers = 18;
+        public const int TotalGpStorageBuffers = 16;
+        public const int TotalRenderTargets    = 8;
+        public const int TotalShaderStages     = 5;
+        public const int TotalVertexBuffers    = 16;
+        public const int TotalViewports        = 8;
+    }
+}

+ 25 - 0
Ryujinx.Graphics.Gpu/Debugging.cs

@@ -0,0 +1,25 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    static class Debugging
+    {
+        public static void PrintTexInfo(string prefix, Image.Texture tex)
+        {
+            if (tex == null)
+            {
+                Console.WriteLine(prefix + " null");
+
+                return;
+            }
+
+            string range = $"{tex.Address:X}..{(tex.Address + tex.Size):X}";
+
+            int debugId = tex.HostTexture.GetStorageDebugId();
+
+            string str = $"{prefix} p {debugId:X8} {tex.Info.Target} {tex.Info.FormatInfo.Format} {tex.Info.Width}x{tex.Info.Height}x{tex.Info.DepthOrLayers} mips {tex.Info.Levels} addr {range}";
+
+            Console.WriteLine(str);
+        }
+    }
+}

+ 16 - 25
Ryujinx.Graphics/DmaPusher.cs → Ryujinx.Graphics.Gpu/DmaPusher.cs

@@ -1,15 +1,14 @@
-using Ryujinx.Graphics.Memory;
 using System.Collections.Concurrent;
 using System.Threading;
 
-namespace Ryujinx.Graphics
+namespace Ryujinx.Graphics.Gpu
 {
     public class DmaPusher
     {
-        private ConcurrentQueue<(NvGpuVmm, long)> _ibBuffer;
+        private ConcurrentQueue<ulong> _ibBuffer;
 
-        private long _dmaPut;
-        private long _dmaGet;
+        private ulong _dmaPut;
+        private ulong _dmaGet;
 
         private struct DmaState
         {
@@ -29,28 +28,26 @@ namespace Ryujinx.Graphics
         private bool _ibEnable;
         private bool _nonMain;
 
-        private long _dmaMGet;
+        private ulong _dmaMGet;
 
-        private NvGpuVmm _vmm;
-
-        private NvGpu _gpu;
+        private GpuContext _context;
 
         private AutoResetEvent _event;
 
-        public DmaPusher(NvGpu gpu)
+        internal DmaPusher(GpuContext context)
         {
-            _gpu = gpu;
+            _context = context;
 
-            _ibBuffer = new ConcurrentQueue<(NvGpuVmm, long)>();
+            _ibBuffer = new ConcurrentQueue<ulong>();
 
             _ibEnable = true;
 
             _event = new AutoResetEvent(false);
         }
 
-        public void Push(NvGpuVmm vmm, long entry)
+        public void Push(ulong entry)
         {
-            _ibBuffer.Enqueue((vmm, entry));
+            _ibBuffer.Enqueue(entry);
 
             _event.Set();
         }
@@ -69,7 +66,7 @@ namespace Ryujinx.Graphics
         {
             if (_dmaGet != _dmaPut)
             {
-                int word = _vmm.ReadInt32(_dmaGet);
+                int word = _context.MemoryAccessor.ReadInt32(_dmaGet);
 
                 _dmaGet += 4;
 
@@ -148,20 +145,14 @@ namespace Ryujinx.Graphics
                     }
                 }
             }
-            else if (_ibEnable && _ibBuffer.TryDequeue(out (NvGpuVmm Vmm, long Entry) tuple))
+            else if (_ibEnable && _ibBuffer.TryDequeue(out ulong entry))
             {
-                _vmm = tuple.Vmm;
-
-                long entry = tuple.Entry;
-
-                int length = (int)(entry >> 42) & 0x1fffff;
+                ulong length = (entry >> 42) & 0x1fffff;
 
                 _dmaGet = entry & 0xfffffffffc;
                 _dmaPut = _dmaGet + length * 4;
 
-                _nonMain = (entry & (1L << 41)) != 0;
-
-                _gpu.ResourceManager.ClearPbCache();
+                _nonMain = (entry & (1UL << 41)) != 0;
             }
             else
             {
@@ -180,7 +171,7 @@ namespace Ryujinx.Graphics
 
         private void CallMethod(int argument)
         {
-            _gpu.Fifo.CallMethod(_vmm, new GpuMethodCall(
+            _context.Fifo.CallMethod(new MethodParams(
                 _state.Method,
                 argument,
                 _state.SubChannel,

+ 83 - 0
Ryujinx.Graphics.Gpu/Engine/Compute.cs

@@ -0,0 +1,83 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        public void Dispatch(int argument)
+        {
+            uint dispatchParamsAddress = (uint)_context.State.Get<int>(MethodOffset.DispatchParamsAddress);
+
+            var dispatchParams = _context.MemoryAccessor.Read<ComputeParams>((ulong)dispatchParamsAddress << 8);
+
+            GpuVa shaderBaseAddress = _context.State.Get<GpuVa>(MethodOffset.ShaderBaseAddress);
+
+            ulong shaderGpuVa = shaderBaseAddress.Pack() + (uint)dispatchParams.ShaderOffset;
+
+            ComputeShader cs = _shaderCache.GetComputeShader(
+                shaderGpuVa,
+                dispatchParams.UnpackBlockSizeX(),
+                dispatchParams.UnpackBlockSizeY(),
+                dispatchParams.UnpackBlockSizeZ());
+
+            _context.Renderer.ComputePipeline.SetProgram(cs.Interface);
+
+            ShaderProgramInfo info = cs.Shader.Info;
+
+            uint sbEnableMask = 0;
+            uint ubEnableMask = dispatchParams.UnpackUniformBuffersEnableMask();
+
+            for (int index = 0; index < dispatchParams.UniformBuffers.Length; index++)
+            {
+                if ((ubEnableMask & (1 << index)) == 0)
+                {
+                    continue;
+                }
+
+                ulong gpuVa = dispatchParams.UniformBuffers[index].PackAddress();
+                ulong size  = dispatchParams.UniformBuffers[index].UnpackSize();
+
+                _bufferManager.SetComputeUniformBuffer(index, gpuVa, size);
+            }
+
+            for (int index = 0; index < info.SBuffers.Count; index++)
+            {
+                BufferDescriptor sb = info.SBuffers[index];
+
+                sbEnableMask |= 1u << sb.Slot;
+
+                ulong sbDescAddress = _bufferManager.GetComputeUniformBufferAddress(0);
+
+                int sbDescOffset = 0x310 + sb.Slot * 0x10;
+
+                sbDescAddress += (ulong)sbDescOffset;
+
+                Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+                SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
+
+                _bufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+            }
+
+            ubEnableMask = 0;
+
+            for (int index = 0; index < info.CBuffers.Count; index++)
+            {
+                ubEnableMask |= 1u << info.CBuffers[index].Slot;
+            }
+
+            _bufferManager.SetComputeStorageBufferEnableMask(sbEnableMask);
+            _bufferManager.SetComputeUniformBufferEnableMask(ubEnableMask);
+
+            _bufferManager.CommitComputeBindings();
+
+            _context.Renderer.ComputePipeline.Dispatch(
+                dispatchParams.UnpackGridSizeX(),
+                dispatchParams.UnpackGridSizeY(),
+                dispatchParams.UnpackGridSizeZ());
+        }
+    }
+}

+ 126 - 0
Ryujinx.Graphics.Gpu/Engine/ComputeParams.cs

@@ -0,0 +1,126 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    struct UniformBufferParams
+    {
+        public int AddressLow;
+        public int AddressHighAndSize;
+
+        public ulong PackAddress()
+        {
+            return (uint)AddressLow | ((ulong)(AddressHighAndSize & 0xff) << 32);
+        }
+
+        public ulong UnpackSize()
+        {
+            return (ulong)((AddressHighAndSize >> 15) & 0x1ffff);
+        }
+    }
+
+    struct ComputeParams
+    {
+        public int Unknown0;
+        public int Unknown1;
+        public int Unknown2;
+        public int Unknown3;
+        public int Unknown4;
+        public int Unknown5;
+        public int Unknown6;
+        public int Unknown7;
+        public int ShaderOffset;
+        public int Unknown9;
+        public int Unknown10;
+        public int Unknown11;
+        public int GridSizeX;
+        public int GridSizeYZ;
+        public int Unknown14;
+        public int Unknown15;
+        public int Unknown16;
+        public int Unknown17;
+        public int BlockSizeX;
+        public int BlockSizeYZ;
+        public int UniformBuffersConfig;
+        public int Unknown21;
+        public int Unknown22;
+        public int Unknown23;
+        public int Unknown24;
+        public int Unknown25;
+        public int Unknown26;
+        public int Unknown27;
+        public int Unknown28;
+
+        private UniformBufferParams _uniformBuffer0;
+        private UniformBufferParams _uniformBuffer1;
+        private UniformBufferParams _uniformBuffer2;
+        private UniformBufferParams _uniformBuffer3;
+        private UniformBufferParams _uniformBuffer4;
+        private UniformBufferParams _uniformBuffer5;
+        private UniformBufferParams _uniformBuffer6;
+        private UniformBufferParams _uniformBuffer7;
+
+        public Span<UniformBufferParams> UniformBuffers
+        {
+            get
+            {
+                return MemoryMarshal.CreateSpan(ref _uniformBuffer0, 8);
+            }
+        }
+
+        public int Unknown45;
+        public int Unknown46;
+        public int Unknown47;
+        public int Unknown48;
+        public int Unknown49;
+        public int Unknown50;
+        public int Unknown51;
+        public int Unknown52;
+        public int Unknown53;
+        public int Unknown54;
+        public int Unknown55;
+        public int Unknown56;
+        public int Unknown57;
+        public int Unknown58;
+        public int Unknown59;
+        public int Unknown60;
+        public int Unknown61;
+        public int Unknown62;
+        public int Unknown63;
+
+        public int UnpackGridSizeX()
+        {
+            return GridSizeX & 0x7fffffff;
+        }
+
+        public int UnpackGridSizeY()
+        {
+            return GridSizeYZ & 0xffff;
+        }
+
+        public int UnpackGridSizeZ()
+        {
+            return (GridSizeYZ >> 16) & 0xffff;
+        }
+
+        public int UnpackBlockSizeX()
+        {
+            return (BlockSizeX >> 16) & 0xffff;
+        }
+
+        public int UnpackBlockSizeY()
+        {
+            return BlockSizeYZ & 0xffff;
+        }
+
+        public int UnpackBlockSizeZ()
+        {
+            return (BlockSizeYZ >> 16) & 0xffff;
+        }
+
+        public uint UnpackUniformBuffersEnableMask()
+        {
+            return (uint)UniformBuffersConfig & 0xff;
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.Gpu/Engine/ComputeShader.cs

@@ -0,0 +1,18 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    class ComputeShader
+    {
+        public IProgram Interface { get; set; }
+
+        public ShaderProgram Shader { get; }
+
+        public ComputeShader(IProgram program, ShaderProgram shader)
+        {
+            Interface = program;
+            Shader    = shader;
+        }
+    }
+}

+ 17 - 0
Ryujinx.Graphics.Gpu/Engine/GraphicsShader.cs

@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Shader;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    class GraphicsShader
+    {
+        public IProgram Interface { get; set; }
+
+        public ShaderProgram[] Shader { get; }
+
+        public GraphicsShader()
+        {
+            Shader = new ShaderProgram[5];
+        }
+    }
+}

+ 42 - 0
Ryujinx.Graphics.Gpu/Engine/Inline2Memory.cs

@@ -0,0 +1,42 @@
+using Ryujinx.Graphics.Gpu.State;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private Inline2MemoryParams _params;
+
+        private bool _isLinear;
+
+        private int _offset;
+        private int _size;
+
+        public void Execute(int argument)
+        {
+            _params = _context.State.Get<Inline2MemoryParams>(MethodOffset.Inline2MemoryParams);
+
+            _isLinear = (argument & 1) != 0;
+
+            _offset = 0;
+            _size   = _params.LineLengthIn * _params.LineCount;
+        }
+
+        public void PushData(int argument)
+        {
+            if (_isLinear)
+            {
+                for (int shift = 0; shift < 32 && _offset < _size; shift += 8, _offset++)
+                {
+                    ulong gpuVa = _params.DstAddress.Pack() + (ulong)_offset;
+
+                    _context.MemoryAccessor.Write(gpuVa, new byte[] { (byte)(argument >> shift) });
+                }
+            }
+            else
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}

+ 55 - 0
Ryujinx.Graphics.Gpu/Engine/MethodClear.cs

@@ -0,0 +1,55 @@
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void Clear(int argument)
+        {
+            UpdateState();
+
+            bool clearDepth   = (argument & 1) != 0;
+            bool clearStencil = (argument & 2) != 0;
+
+            uint componentMask = (uint)((argument >> 2) & 0xf);
+
+            int index = (argument >> 6) & 0xf;
+
+            if (componentMask != 0)
+            {
+                ClearColors clearColor = _context.State.GetClearColors();
+
+                ColorF color = new ColorF(
+                    clearColor.Red,
+                    clearColor.Green,
+                    clearColor.Blue,
+                    clearColor.Alpha);
+
+                _context.Renderer.GraphicsPipeline.ClearRenderTargetColor(
+                    index,
+                    componentMask,
+                    color);
+            }
+
+            if (clearDepth || clearStencil)
+            {
+                float depthValue   = _context.State.GetClearDepthValue();
+                int   stencilValue = _context.State.GetClearStencilValue();
+
+                int stencilMask = 0;
+
+                if (clearStencil)
+                {
+                    stencilMask = _context.State.GetStencilTestState().FrontMask;
+                }
+
+                _context.Renderer.GraphicsPipeline.ClearRenderTargetDepthStencil(
+                    depthValue,
+                    clearDepth,
+                    stencilValue,
+                    stencilMask);
+            }
+        }
+    }
+}

+ 79 - 0
Ryujinx.Graphics.Gpu/Engine/MethodCopyBuffer.cs

@@ -0,0 +1,79 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void CopyBuffer(int argument)
+        {
+            var cbp = _context.State.Get<CopyBufferParams>(MethodOffset.CopyBufferParams);
+
+            var swizzle = _context.State.Get<CopyBufferSwizzle>(MethodOffset.CopyBufferSwizzle);
+
+            bool srcLinear = (argument & (1 << 7)) != 0;
+            bool dstLinear = (argument & (1 << 8)) != 0;
+            bool copy2D    = (argument & (1 << 9)) != 0;
+
+            int size = cbp.XCount;
+
+            if (size == 0)
+            {
+                return;
+            }
+
+            if (copy2D)
+            {
+                // Buffer to texture copy.
+                int srcBpp = swizzle.UnpackSrcComponentsCount() * swizzle.UnpackComponentSize();
+                int dstBpp = swizzle.UnpackDstComponentsCount() * swizzle.UnpackComponentSize();
+
+                var dst = _context.State.Get<CopyBufferTexture>(MethodOffset.CopyBufferDstTexture);
+                var src = _context.State.Get<CopyBufferTexture>(MethodOffset.CopyBufferSrcTexture);
+
+                var srcCalculator = new OffsetCalculator(
+                    src.Width,
+                    src.Height,
+                    cbp.SrcStride,
+                    srcLinear,
+                    src.MemoryLayout.UnpackGobBlocksInY(),
+                    srcBpp);
+
+                var dstCalculator = new OffsetCalculator(
+                    dst.Width,
+                    dst.Height,
+                    cbp.DstStride,
+                    dstLinear,
+                    dst.MemoryLayout.UnpackGobBlocksInY(),
+                    dstBpp);
+
+                ulong srcBaseAddress = _context.MemoryManager.Translate(cbp.SrcAddress.Pack());
+                ulong dstBaseAddress = _context.MemoryManager.Translate(cbp.DstAddress.Pack());
+
+                for (int y = 0; y < cbp.YCount; y++)
+                for (int x = 0; x < cbp.XCount; x++)
+                {
+                    int srcOffset = srcCalculator.GetOffset(src.RegionX + x, src.RegionY + y);
+                    int dstOffset = dstCalculator.GetOffset(dst.RegionX + x, dst.RegionY + y);
+
+                    ulong srcAddress = srcBaseAddress + (ulong)srcOffset;
+                    ulong dstAddress = dstBaseAddress + (ulong)dstOffset;
+
+                    Span<byte> pixel = _context.PhysicalMemory.Read(srcAddress, (ulong)srcBpp);
+
+                    _context.PhysicalMemory.Write(dstAddress, pixel);
+                }
+            }
+            else
+            {
+                // Buffer to buffer copy.
+                _bufferManager.CopyBuffer(cbp.SrcAddress, cbp.DstAddress, (uint)size);
+
+                Span<byte> data = _context.MemoryAccessor.Read(cbp.SrcAddress.Pack(), (uint)size);
+
+                _context.MemoryAccessor.Write(cbp.DstAddress.Pack(), data);
+            }
+        }
+    }
+}

+ 70 - 0
Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs

@@ -0,0 +1,70 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void CopyTexture(int argument)
+        {
+            CopyTexture dstCopyTexture = _context.State.GetCopyDstTexture();
+            CopyTexture srcCopyTexture = _context.State.GetCopySrcTexture();
+
+            Image.Texture srcTexture = _textureManager.FindOrCreateTexture(srcCopyTexture);
+
+            if (srcTexture == null)
+            {
+                return;
+            }
+
+            // When the source texture that was found has a depth format,
+            // we must enforce the target texture also has a depth format,
+            // as copies between depth and color formats are not allowed.
+            if (srcTexture.Format == Format.D32Float)
+            {
+                dstCopyTexture.Format = RtFormat.D32Float;
+            }
+
+            Image.Texture dstTexture = _textureManager.FindOrCreateTexture(dstCopyTexture);
+
+            if (dstTexture == null)
+            {
+                return;
+            }
+
+            CopyTextureControl control = _context.State.GetCopyTextureControl();
+
+            CopyRegion region = _context.State.GetCopyRegion();
+
+            int srcX1 = (int)(region.SrcXF >> 32);
+            int srcY1 = (int)(region.SrcYF >> 32);
+
+            int srcX2 = (int)((region.SrcXF + region.SrcWidthRF  * region.DstWidth)  >> 32);
+            int srcY2 = (int)((region.SrcYF + region.SrcHeightRF * region.DstHeight) >> 32);
+
+            int dstX1 = region.DstX;
+            int dstY1 = region.DstY;
+
+            int dstX2 = region.DstX + region.DstWidth;
+            int dstY2 = region.DstY + region.DstHeight;
+
+            Extents2D srcRegion = new Extents2D(
+                srcX1 / srcTexture.Info.SamplesInX,
+                srcY1 / srcTexture.Info.SamplesInY,
+                srcX2 / srcTexture.Info.SamplesInX,
+                srcY2 / srcTexture.Info.SamplesInY);
+
+            Extents2D dstRegion = new Extents2D(
+                dstX1 / dstTexture.Info.SamplesInX,
+                dstY1 / dstTexture.Info.SamplesInY,
+                dstX2 / dstTexture.Info.SamplesInX,
+                dstY2 / dstTexture.Info.SamplesInY);
+
+            bool linearFilter = control.UnpackLinearFilter();
+
+            srcTexture.HostTexture.CopyTo(dstTexture.HostTexture, srcRegion, dstRegion, linearFilter);
+
+            dstTexture.Modified = true;
+        }
+    }
+}

+ 133 - 0
Ryujinx.Graphics.Gpu/Engine/MethodDraw.cs

@@ -0,0 +1,133 @@
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Gpu.Image;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private bool _drawIndexed;
+
+        private int _firstIndex;
+        private int _indexCount;
+
+        private bool _instancedHasState;
+        private bool _instancedIndexed;
+
+        private int _instancedFirstIndex;
+        private int _instancedFirstVertex;
+        private int _instancedFirstInstance;
+        private int _instancedIndexCount;
+        private int _instancedDrawStateFirst;
+        private int _instancedDrawStateCount;
+
+        private int _instanceIndex;
+
+        public PrimitiveType PrimitiveType { get; private set; }
+
+        private void DrawEnd(int argument)
+        {
+            UpdateState();
+
+            bool instanced = _vsUsesInstanceId || _isAnyVbInstanced;
+
+            if (instanced)
+            {
+                if (!_instancedHasState)
+                {
+                    _instancedHasState = true;
+
+                    _instancedIndexed = _drawIndexed;
+
+                    _instancedFirstIndex    = _firstIndex;
+                    _instancedFirstVertex   = _context.State.GetBaseVertex();
+                    _instancedFirstInstance = _context.State.GetBaseInstance();
+
+                    _instancedIndexCount = _indexCount;
+
+                    VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+                    _instancedDrawStateFirst = drawState.First;
+                    _instancedDrawStateCount = drawState.Count;
+                }
+
+                return;
+            }
+
+            int firstInstance = _context.State.GetBaseInstance();
+
+            if (_drawIndexed)
+            {
+                _drawIndexed = false;
+
+                int firstVertex = _context.State.GetBaseVertex();
+
+                _context.Renderer.GraphicsPipeline.DrawIndexed(
+                    _indexCount,
+                    1,
+                    _firstIndex,
+                    firstVertex,
+                    firstInstance);
+            }
+            else
+            {
+                VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+                _context.Renderer.GraphicsPipeline.Draw(
+                    drawState.Count,
+                    1,
+                    drawState.First,
+                    firstInstance);
+            }
+        }
+
+        private void DrawBegin(int argument)
+        {
+            PrimitiveType type = (PrimitiveType)(argument & 0xffff);
+
+            _context.Renderer.GraphicsPipeline.SetPrimitiveTopology(type.Convert());
+
+            PrimitiveType = type;
+
+            if ((argument & (1 << 26)) != 0)
+            {
+                _instanceIndex++;
+            }
+            else if ((argument & (1 << 27)) == 0)
+            {
+                _instanceIndex = 0;
+            }
+        }
+
+        private void SetIndexCount(int argument)
+        {
+            _drawIndexed = true;
+        }
+
+        public void PerformDeferredDraws()
+        {
+            // Perform any pending instanced draw.
+            if (_instancedHasState)
+            {
+                _instancedHasState = false;
+
+                if (_instancedIndexed)
+                {
+                    _context.Renderer.GraphicsPipeline.DrawIndexed(
+                        _instancedIndexCount,
+                        _instanceIndex + 1,
+                        _instancedFirstIndex,
+                        _instancedFirstVertex,
+                        _instancedFirstInstance);
+                }
+                else
+                {
+                    _context.Renderer.GraphicsPipeline.Draw(
+                        _instancedDrawStateCount,
+                        _instanceIndex + 1,
+                        _instancedDrawStateFirst,
+                        _instancedFirstInstance);
+                }
+            }
+        }
+    }
+}

+ 100 - 0
Ryujinx.Graphics.Gpu/Engine/MethodReport.cs

@@ -0,0 +1,100 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private ulong _runningCounter;
+
+        private void Report(int argument)
+        {
+            ReportMode mode = (ReportMode)(argument & 3);
+
+            ReportCounterType type = (ReportCounterType)((argument >> 23) & 0x1f);
+
+            switch (mode)
+            {
+                case ReportMode.Semaphore: ReportSemaphore();   break;
+                case ReportMode.Counter:   ReportCounter(type); break;
+            }
+        }
+
+        private void ReportSemaphore()
+        {
+            ReportState state = _context.State.GetReportState();
+
+            _context.MemoryAccessor.Write(state.Address.Pack(), state.Payload);
+
+            _context.AdvanceSequence();
+        }
+
+        private struct CounterData
+        {
+            public ulong Counter;
+            public ulong Timestamp;
+        }
+
+        private void ReportCounter(ReportCounterType type)
+        {
+            CounterData counterData = new CounterData();
+
+            ulong counter = 0;
+
+            switch (type)
+            {
+                case ReportCounterType.Zero:
+                    counter = 0;
+                    break;
+                case ReportCounterType.SamplesPassed:
+                    counter = _context.Renderer.GetCounter(CounterType.SamplesPassed);
+                    break;
+                case ReportCounterType.PrimitivesGenerated:
+                    counter = _context.Renderer.GetCounter(CounterType.PrimitivesGenerated);
+                    break;
+                case ReportCounterType.TransformFeedbackPrimitivesWritten:
+                    counter = _context.Renderer.GetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+                    break;
+            }
+
+            ulong ticks;
+
+            if (GraphicsConfig.FastGpuTime)
+            {
+                ticks = _runningCounter++;
+            }
+            else
+            {
+                ticks = ConvertNanosecondsToTicks((ulong)PerformanceCounter.ElapsedNanoseconds);
+            }
+
+            counterData.Counter   = counter;
+            counterData.Timestamp = ticks;
+
+            Span<CounterData> counterDataSpan = MemoryMarshal.CreateSpan(ref counterData, 1);
+
+            Span<byte> data = MemoryMarshal.Cast<CounterData, byte>(counterDataSpan);
+
+            ReportState state = _context.State.GetReportState();
+
+            _context.MemoryAccessor.Write(state.Address.Pack(), data);
+        }
+
+        private static ulong ConvertNanosecondsToTicks(ulong nanoseconds)
+        {
+            // We need to divide first to avoid overflows.
+            // We fix up the result later by calculating the difference and adding
+            // that to the result.
+            ulong divided = nanoseconds / 625;
+
+            ulong rounded = divided * 625;
+
+            ulong errorBias = ((nanoseconds - rounded) * 384) / 625;
+
+            return divided * 384 + errorBias;
+        }
+    }
+}

+ 26 - 0
Ryujinx.Graphics.Gpu/Engine/MethodResetCounter.cs

@@ -0,0 +1,26 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void ResetCounter(int argument)
+        {
+            ResetCounterType type = (ResetCounterType)argument;
+
+            switch (type)
+            {
+                case ResetCounterType.SamplesPassed:
+                    _context.Renderer.ResetCounter(CounterType.SamplesPassed);
+                    break;
+                case ResetCounterType.PrimitivesGenerated:
+                    _context.Renderer.ResetCounter(CounterType.PrimitivesGenerated);
+                    break;
+                case ResetCounterType.TransformFeedbackPrimitivesWritten:
+                    _context.Renderer.ResetCounter(CounterType.TransformFeedbackPrimitivesWritten);
+                    break;
+            }
+        }
+    }
+}

+ 52 - 0
Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferBind.cs

@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void UniformBufferBind0(int argument)
+        {
+            UniformBufferBind(argument, ShaderType.Vertex);
+        }
+
+        private void UniformBufferBind1(int argument)
+        {
+            UniformBufferBind(argument, ShaderType.TessellationControl);
+        }
+
+        private void UniformBufferBind2(int argument)
+        {
+            UniformBufferBind(argument, ShaderType.TessellationEvaluation);
+        }
+
+        private void UniformBufferBind3(int argument)
+        {
+            UniformBufferBind(argument, ShaderType.Geometry);
+        }
+
+        private void UniformBufferBind4(int argument)
+        {
+            UniformBufferBind(argument, ShaderType.Fragment);
+        }
+
+        private void UniformBufferBind(int argument, ShaderType type)
+        {
+            bool enable = (argument & 1) != 0;
+
+            int index = (argument >> 4) & 0x1f;
+
+            if (enable)
+            {
+                UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
+
+                ulong address = uniformBuffer.Address.Pack();
+
+                _bufferManager.SetGraphicsUniformBuffer((int)type, index, address, (uint)uniformBuffer.Size);
+            }
+            else
+            {
+                _bufferManager.SetGraphicsUniformBuffer((int)type, index, 0, 0);
+            }
+        }
+    }
+}

+ 18 - 0
Ryujinx.Graphics.Gpu/Engine/MethodUniformBufferUpdate.cs

@@ -0,0 +1,18 @@
+using Ryujinx.Graphics.Gpu.State;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private void UniformBufferUpdate(int argument)
+        {
+            UniformBufferState uniformBuffer = _context.State.GetUniformBufferState();
+
+            _context.MemoryAccessor.Write(uniformBuffer.Address.Pack() + (uint)uniformBuffer.Offset, argument);
+
+            _context.State.SetUniformBufferOffset(uniformBuffer.Offset + 4);
+
+            _context.AdvanceSequence();
+        }
+    }
+}

+ 784 - 0
Ryujinx.Graphics.Gpu/Engine/Methods.cs

@@ -0,0 +1,784 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Blend;
+using Ryujinx.Graphics.GAL.DepthStencil;
+using Ryujinx.Graphics.GAL.InputAssembler;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    partial class Methods
+    {
+        private GpuContext _context;
+
+        private ShaderCache _shaderCache;
+
+        private BufferManager  _bufferManager;
+        private TextureManager _textureManager;
+
+        public TextureManager TextureManager => _textureManager;
+
+        private bool _isAnyVbInstanced;
+        private bool _vsUsesInstanceId;
+
+        public Methods(GpuContext context)
+        {
+            _context = context;
+
+            _shaderCache = new ShaderCache(_context);
+
+            _bufferManager  = new BufferManager(context);
+            _textureManager = new TextureManager(context, _bufferManager);
+
+            RegisterCallbacks();
+        }
+
+        private void RegisterCallbacks()
+        {
+            _context.State.RegisterCopyBufferCallback(CopyBuffer);
+            _context.State.RegisterCopyTextureCallback(CopyTexture);
+
+            _context.State.RegisterDrawEndCallback(DrawEnd);
+
+            _context.State.RegisterDrawBeginCallback(DrawBegin);
+
+            _context.State.RegisterSetIndexCountCallback(SetIndexCount);
+
+            _context.State.RegisterClearCallback(Clear);
+
+            _context.State.RegisterReportCallback(Report);
+
+            _context.State.RegisterUniformBufferUpdateCallback(UniformBufferUpdate);
+
+            _context.State.RegisterUniformBufferBind0Callback(UniformBufferBind0);
+            _context.State.RegisterUniformBufferBind1Callback(UniformBufferBind1);
+            _context.State.RegisterUniformBufferBind2Callback(UniformBufferBind2);
+            _context.State.RegisterUniformBufferBind3Callback(UniformBufferBind3);
+            _context.State.RegisterUniformBufferBind4Callback(UniformBufferBind4);
+
+            _context.State.RegisterCallback(MethodOffset.InvalidateTextures, InvalidateTextures);
+
+            _context.State.RegisterCallback(MethodOffset.ResetCounter, ResetCounter);
+
+            _context.State.RegisterCallback(MethodOffset.Inline2MemoryExecute,  Execute);
+            _context.State.RegisterCallback(MethodOffset.Inline2MemoryPushData, PushData);
+
+            _context.State.RegisterCallback(MethodOffset.Dispatch, Dispatch);
+        }
+
+        public Image.Texture GetTexture(ulong address) => _textureManager.Find2(address);
+
+        private void UpdateState()
+        {
+            if ((_context.State.StateWriteFlags & StateWriteFlags.Any) == 0)
+            {
+                CommitBindings();
+
+                return;
+            }
+
+            // Shaders must be the first one to be updated if modified, because
+            // some of the other state depends on information from the currently
+            // bound shaders.
+            if ((_context.State.StateWriteFlags & StateWriteFlags.ShaderState) != 0)
+            {
+                UpdateShaderState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.RenderTargetGroup) != 0)
+            {
+                UpdateRenderTargetGroupState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.DepthTestState) != 0)
+            {
+                UpdateDepthTestState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.ViewportTransform) != 0)
+            {
+                UpdateViewportTransform();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.DepthBiasState) != 0)
+            {
+                UpdateDepthBiasState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.StencilTestState) != 0)
+            {
+                UpdateStencilTestState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.SamplerPoolState) != 0)
+            {
+                UpdateSamplerPoolState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.TexturePoolState) != 0)
+            {
+                UpdateTexturePoolState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.InputAssemblerGroup) != 0)
+            {
+                UpdateInputAssemblerGroupState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.FaceState) != 0)
+            {
+                UpdateFaceState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.RtColorMask) != 0)
+            {
+                UpdateRtColorMask();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.BlendState) != 0)
+            {
+                UpdateBlendState();
+            }
+
+            _context.State.StateWriteFlags &= ~StateWriteFlags.Any;
+
+            CommitBindings();
+        }
+
+        private void CommitBindings()
+        {
+            _bufferManager.CommitBindings();
+            _textureManager.CommitBindings();
+        }
+
+        public void InvalidateRange(ulong address, ulong size)
+        {
+            _bufferManager.InvalidateRange(address, size);
+            _textureManager.InvalidateRange(address, size);
+        }
+
+        public void InvalidateTextureRange(ulong address, ulong size)
+        {
+            _textureManager.InvalidateRange(address, size);
+        }
+
+        private void UpdateRenderTargetGroupState()
+        {
+            TextureMsaaMode msaaMode = _context.State.GetRtMsaaMode();
+
+            int samplesInX = msaaMode.SamplesInX();
+            int samplesInY = msaaMode.SamplesInY();
+
+            Image.Texture color3D = Get3DRenderTarget(samplesInX, samplesInY);
+
+            if (color3D == null)
+            {
+                for (int index = 0; index < Constants.TotalRenderTargets; index++)
+                {
+                    RtColorState colorState = _context.State.GetRtColorState(index);
+
+                    if (!IsRtEnabled(colorState))
+                    {
+                        _textureManager.SetRenderTargetColor(index, null);
+
+                        continue;
+                    }
+
+                    Image.Texture color = _textureManager.FindOrCreateTexture(
+                        colorState,
+                        samplesInX,
+                        samplesInY);
+
+                    _textureManager.SetRenderTargetColor(index, color);
+
+                    color.Modified = true;
+                }
+            }
+            else
+            {
+                _textureManager.SetRenderTargetColor3D(color3D);
+
+                color3D.Modified = true;
+            }
+
+            bool dsEnable = _context.State.Get<bool>(MethodOffset.RtDepthStencilEnable);
+
+            Image.Texture depthStencil = null;
+
+            if (dsEnable)
+            {
+                var dsState = _context.State.GetRtDepthStencilState();
+                var dsSize  = _context.State.GetRtDepthStencilSize();
+
+                depthStencil = _textureManager.FindOrCreateTexture(
+                    dsState,
+                    dsSize,
+                    samplesInX,
+                    samplesInY);
+            }
+
+            _textureManager.SetRenderTargetDepthStencil(depthStencil);
+        }
+
+        private Image.Texture Get3DRenderTarget(int samplesInX, int samplesInY)
+        {
+            RtColorState colorState0 = _context.State.GetRtColorState(0);
+
+            if (!IsRtEnabled(colorState0) || !colorState0.MemoryLayout.UnpackIsTarget3D() || colorState0.Depth != 1)
+            {
+                return null;
+            }
+
+            int slices = 1;
+            int unused = 0;
+
+            for (int index = 1; index < Constants.TotalRenderTargets; index++)
+            {
+                RtColorState colorState = _context.State.GetRtColorState(index);
+
+                if (!IsRtEnabled(colorState))
+                {
+                    unused++;
+
+                    continue;
+                }
+
+                if (colorState.MemoryLayout.UnpackIsTarget3D() && colorState.Depth == 1)
+                {
+                    slices++;
+                }
+            }
+
+            if (slices + unused == Constants.TotalRenderTargets)
+            {
+                colorState0.Depth = slices;
+
+                return _textureManager.FindOrCreateTexture(colorState0, samplesInX, samplesInY);
+            }
+
+            return null;
+        }
+
+        private static bool IsRtEnabled(RtColorState colorState)
+        {
+            // Colors are disabled by writing 0 to the format.
+            return colorState.Format != 0 && colorState.WidthOrStride != 0;
+        }
+
+        private void UpdateDepthTestState()
+        {
+            _context.Renderer.GraphicsPipeline.SetDepthTest(new DepthTestDescriptor(
+                _context.State.GetDepthTestEnable().IsTrue(),
+                _context.State.GetDepthWriteEnable().IsTrue(),
+                _context.State.GetDepthTestFunc()));
+        }
+
+        private void UpdateViewportTransform()
+        {
+            Viewport[] viewports = new Viewport[Constants.TotalViewports];
+
+            for (int index = 0; index < Constants.TotalViewports; index++)
+            {
+                var transform = _context.State.Get<ViewportTransform>(MethodOffset.ViewportTransform + index * 8);
+                var extents   = _context.State.Get<ViewportExtents>  (MethodOffset.ViewportExtents   + index * 4);
+
+                float x = transform.TranslateX - MathF.Abs(transform.ScaleX);
+                float y = transform.TranslateY - MathF.Abs(transform.ScaleY);
+
+                float width  = transform.ScaleX * 2;
+                float height = transform.ScaleY * 2;
+
+                RectangleF region = new RectangleF(x, y, width, height);
+
+                viewports[index] = new Viewport(
+                    region,
+                    transform.UnpackSwizzleX(),
+                    transform.UnpackSwizzleY(),
+                    transform.UnpackSwizzleZ(),
+                    transform.UnpackSwizzleW(),
+                    extents.DepthNear,
+                    extents.DepthFar);
+            }
+
+            _context.Renderer.GraphicsPipeline.SetViewports(0, viewports);
+        }
+
+        private void UpdateDepthBiasState()
+        {
+            var polygonOffset = _context.State.Get<DepthBiasState>(MethodOffset.DepthBiasState);
+
+            float factor = _context.State.Get<float>(MethodOffset.DepthBiasFactor);
+            float units  = _context.State.Get<float>(MethodOffset.DepthBiasUnits);
+            float clamp  = _context.State.Get<float>(MethodOffset.DepthBiasClamp);
+
+            PolygonModeMask enables = 0;
+
+            enables  = (polygonOffset.PointEnable.IsTrue() ? PolygonModeMask.Point : 0);
+            enables |= (polygonOffset.LineEnable.IsTrue()  ? PolygonModeMask.Line  : 0);
+            enables |= (polygonOffset.FillEnable.IsTrue()  ? PolygonModeMask.Fill  : 0);
+
+            _context.Renderer.GraphicsPipeline.SetDepthBias(enables, factor, units, clamp);
+        }
+
+        private void UpdateStencilTestState()
+        {
+            StencilBackMasks     backMasks = _context.State.GetStencilBackMasks();
+            StencilTestState     test      = _context.State.GetStencilTestState();
+            StencilBackTestState backTest  = _context.State.GetStencilBackTestState();
+
+            CompareOp backFunc;
+            StencilOp backSFail;
+            StencilOp backDpPass;
+            StencilOp backDpFail;
+            int       backFuncRef;
+            int       backFuncMask;
+            int       backMask;
+
+            if (backTest.TwoSided.IsTrue())
+            {
+                backFunc     = backTest.BackFunc;
+                backSFail    = backTest.BackSFail;
+                backDpPass   = backTest.BackDpPass;
+                backDpFail   = backTest.BackDpFail;
+                backFuncRef  = backMasks.FuncRef;
+                backFuncMask = backMasks.FuncMask;
+                backMask     = backMasks.Mask;
+            }
+            else
+            {
+                backFunc     = test.FrontFunc;
+                backSFail    = test.FrontSFail;
+                backDpPass   = test.FrontDpPass;
+                backDpFail   = test.FrontDpFail;
+                backFuncRef  = test.FrontFuncRef;
+                backFuncMask = test.FrontFuncMask;
+                backMask     = test.FrontMask;
+            }
+
+            _context.Renderer.GraphicsPipeline.SetStencilTest(new StencilTestDescriptor(
+                test.Enable.IsTrue(),
+                test.FrontFunc,
+                test.FrontSFail,
+                test.FrontDpPass,
+                test.FrontDpFail,
+                test.FrontFuncRef,
+                test.FrontFuncMask,
+                test.FrontMask,
+                backFunc,
+                backSFail,
+                backDpPass,
+                backDpFail,
+                backFuncRef,
+                backFuncMask,
+                backMask));
+        }
+
+        private void UpdateSamplerPoolState()
+        {
+            PoolState samplerPool = _context.State.GetSamplerPoolState();
+
+            _textureManager.SetSamplerPool(samplerPool.Address.Pack(), samplerPool.MaximumId);
+        }
+
+        private void UpdateTexturePoolState()
+        {
+            PoolState texturePool = _context.State.GetTexturePoolState();
+
+            _textureManager.SetTexturePool(texturePool.Address.Pack(), texturePool.MaximumId);
+
+            _textureManager.SetTextureBufferIndex(_context.State.GetTextureBufferIndex());
+        }
+
+        private void UpdateInputAssemblerGroupState()
+        {
+            // Must be updated before the vertex buffer.
+            if ((_context.State.StateWriteFlags & StateWriteFlags.VertexAttribState) != 0)
+            {
+                UpdateVertexAttribState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.PrimitiveRestartState) != 0)
+            {
+                UpdatePrimitiveRestartState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.IndexBufferState) != 0)
+            {
+                UpdateIndexBufferState();
+            }
+
+            if ((_context.State.StateWriteFlags & StateWriteFlags.VertexBufferState) != 0)
+            {
+                UpdateVertexBufferState();
+            }
+        }
+
+        private void UpdateVertexAttribState()
+        {
+            VertexAttribDescriptor[] vertexAttribs = new VertexAttribDescriptor[16];
+
+            for (int index = 0; index < 16; index++)
+            {
+                VertexAttribState vertexAttrib = _context.State.GetVertexAttribState(index);
+
+                if (!FormatTable.TryGetAttribFormat(vertexAttrib.UnpackFormat(), out Format format))
+                {
+                    // TODO: warning.
+
+                    format = Format.R32G32B32A32Float;
+                }
+
+                vertexAttribs[index] = new VertexAttribDescriptor(
+                    vertexAttrib.UnpackBufferIndex(),
+                    vertexAttrib.UnpackOffset(),
+                    format);
+            }
+
+            _context.Renderer.GraphicsPipeline.BindVertexAttribs(vertexAttribs);
+        }
+
+        private void UpdatePrimitiveRestartState()
+        {
+            PrimitiveRestartState primitiveRestart = _context.State.Get<PrimitiveRestartState>(MethodOffset.PrimitiveRestartState);
+
+            _context.Renderer.GraphicsPipeline.SetPrimitiveRestart(
+                primitiveRestart.Enable,
+                primitiveRestart.Index);
+        }
+
+        private void UpdateIndexBufferState()
+        {
+            IndexBufferState indexBuffer = _context.State.GetIndexBufferState();
+
+            _firstIndex = indexBuffer.First;
+            _indexCount = indexBuffer.Count;
+
+            if (_indexCount == 0)
+            {
+                return;
+            }
+
+            ulong gpuVa = indexBuffer.Address.Pack();
+
+            // Do not use the end address to calculate the size, because
+            // the result may be much larger than the real size of the index buffer.
+            ulong size = (ulong)(_firstIndex + _indexCount);
+
+            switch (indexBuffer.Type)
+            {
+                case IndexType.UShort: size *= 2; break;
+                case IndexType.UInt:   size *= 4; break;
+            }
+
+            _bufferManager.SetIndexBuffer(gpuVa, size, indexBuffer.Type);
+
+            // The index buffer affects the vertex buffer size calculation, we
+            // need to ensure that they are updated.
+            UpdateVertexBufferState();
+        }
+
+        private uint GetIndexBufferMaxIndex(ulong gpuVa, ulong size, IndexType type)
+        {
+            ulong address = _context.MemoryManager.Translate(gpuVa);
+
+            Span<byte> data = _context.PhysicalMemory.Read(address, size);
+
+            uint maxIndex = 0;
+
+            switch (type)
+            {
+                case IndexType.UByte:
+                {
+                    for (int index = 0; index < data.Length; index++)
+                    {
+                        if (maxIndex < data[index])
+                        {
+                            maxIndex = data[index];
+                        }
+                    }
+
+                    break;
+                }
+
+                case IndexType.UShort:
+                {
+                    Span<ushort> indices = MemoryMarshal.Cast<byte, ushort>(data);
+
+                    for (int index = 0; index < indices.Length; index++)
+                    {
+                        if (maxIndex < indices[index])
+                        {
+                            maxIndex = indices[index];
+                        }
+                    }
+
+                    break;
+                }
+
+                case IndexType.UInt:
+                {
+                    Span<uint> indices = MemoryMarshal.Cast<byte, uint>(data);
+
+                    for (int index = 0; index < indices.Length; index++)
+                    {
+                        if (maxIndex < indices[index])
+                        {
+                            maxIndex = indices[index];
+                        }
+                    }
+
+                    break;
+                }
+            }
+
+            return maxIndex;
+        }
+
+        private void UpdateVertexBufferState()
+        {
+            _isAnyVbInstanced = false;
+
+            for (int index = 0; index < 16; index++)
+            {
+                VertexBufferState vertexBuffer = _context.State.GetVertexBufferState(index);
+
+                if (!vertexBuffer.UnpackEnable())
+                {
+                    _bufferManager.SetVertexBuffer(index, 0, 0, 0, 0);
+
+                    continue;
+                }
+
+                GpuVa endAddress = _context.State.GetVertexBufferEndAddress(index);
+
+                ulong address = vertexBuffer.Address.Pack();
+
+                int stride = vertexBuffer.UnpackStride();
+
+                bool instanced = _context.State.Get<bool>(MethodOffset.VertexBufferInstanced + index);
+
+                int divisor = instanced ? vertexBuffer.Divisor : 0;
+
+                _isAnyVbInstanced |= divisor != 0;
+
+                ulong size;
+
+                if (_drawIndexed || stride == 0 || instanced)
+                {
+                    // This size may be (much) larger than the real vertex buffer size.
+                    // Avoid calculating it this way, unless we don't have any other option.
+                    size = endAddress.Pack() - address + 1;
+                }
+                else
+                {
+                    // For non-indexed draws, we can guess the size from the vertex count
+                    // and stride.
+                    int firstInstance = _context.State.GetBaseInstance();
+
+                    VertexBufferDrawState drawState = _context.State.GetVertexBufferDrawState();
+
+                    size = (ulong)((firstInstance + drawState.First + drawState.Count) * stride);
+                }
+
+                _bufferManager.SetVertexBuffer(index, address, size, stride, divisor);
+            }
+        }
+
+        private void UpdateFaceState()
+        {
+            FaceState face = _context.State.GetFaceState();
+
+            _context.Renderer.GraphicsPipeline.SetFaceCulling(face.CullEnable.IsTrue(), face.CullFace);
+
+            _context.Renderer.GraphicsPipeline.SetFrontFace(face.FrontFace);
+        }
+
+        private void UpdateRtColorMask()
+        {
+            uint[] componentMasks = new uint[Constants.TotalRenderTargets];
+
+            for (int index = 0; index < Constants.TotalRenderTargets; index++)
+            {
+                RtColorMask colorMask = _context.State.Get<RtColorMask>(MethodOffset.RtColorMask + index);
+
+                uint componentMask = 0;
+
+                componentMask  = (colorMask.UnpackRed()   ? 1u : 0u);
+                componentMask |= (colorMask.UnpackGreen() ? 2u : 0u);
+                componentMask |= (colorMask.UnpackBlue()  ? 4u : 0u);
+                componentMask |= (colorMask.UnpackAlpha() ? 8u : 0u);
+
+                componentMasks[index] = componentMask;
+            }
+
+            _context.Renderer.GraphicsPipeline.SetRenderTargetColorMasks(componentMasks);
+        }
+
+        private void UpdateBlendState()
+        {
+            BlendState[] blends = new BlendState[8];
+
+            for (int index = 0; index < 8; index++)
+            {
+                bool blendEnable = _context.State.GetBlendEnable(index).IsTrue();
+
+                BlendState blend = _context.State.GetBlendState(index);
+
+                BlendDescriptor descriptor = new BlendDescriptor(
+                    blendEnable,
+                    blend.ColorOp,
+                    blend.ColorSrcFactor,
+                    blend.ColorDstFactor,
+                    blend.AlphaOp,
+                    blend.AlphaSrcFactor,
+                    blend.AlphaDstFactor);
+
+                _context.Renderer.GraphicsPipeline.BindBlendState(index, descriptor);
+            }
+        }
+
+        private struct SbDescriptor
+        {
+            public uint AddressLow;
+            public uint AddressHigh;
+            public int  Size;
+            public int  Padding;
+
+            public ulong PackAddress()
+            {
+                return AddressLow | ((ulong)AddressHigh << 32);
+            }
+        }
+
+        private void UpdateShaderState()
+        {
+            ShaderAddresses addresses = new ShaderAddresses();
+
+            Span<ShaderAddresses> addressesSpan = MemoryMarshal.CreateSpan(ref addresses, 1);
+
+            Span<ulong> addressesArray = MemoryMarshal.Cast<ShaderAddresses, ulong>(addressesSpan);
+
+            ulong baseAddress = _context.State.GetShaderBaseAddress().Pack();
+
+            for (int index = 0; index < 6; index++)
+            {
+                ShaderState shader = _context.State.GetShaderState(index);
+
+                if (!shader.UnpackEnable() && index != 1)
+                {
+                    continue;
+                }
+
+                addressesArray[index] = baseAddress + shader.Offset;
+            }
+
+            GraphicsShader gs = _shaderCache.GetGraphicsShader(addresses);
+
+            _vsUsesInstanceId = gs.Shader[0].Info.UsesInstanceId;
+
+            for (int stage = 0; stage < Constants.TotalShaderStages; stage++)
+            {
+                ShaderProgramInfo info = gs.Shader[stage]?.Info;
+
+                if (info == null)
+                {
+                    continue;
+                }
+
+                var textureBindings = new TextureBindingInfo[info.Textures.Count];
+
+                for (int index = 0; index < info.Textures.Count; index++)
+                {
+                    var descriptor = info.Textures[index];
+
+                    Target target = GetTarget(descriptor.Target);
+
+                    textureBindings[index] = new TextureBindingInfo(target, descriptor.HandleIndex);
+                }
+
+                _textureManager.BindTextures(stage, textureBindings);
+
+                uint sbEnableMask = 0;
+                uint ubEnableMask = 0;
+
+                for (int index = 0; index < info.SBuffers.Count; index++)
+                {
+                    BufferDescriptor sb = info.SBuffers[index];
+
+                    sbEnableMask |= 1u << sb.Slot;
+
+                    ulong sbDescAddress = _bufferManager.GetGraphicsUniformBufferAddress(stage, 0);
+
+                    int sbDescOffset = 0x110 + stage * 0x100 + sb.Slot * 0x10;
+
+                    sbDescAddress += (ulong)sbDescOffset;
+
+                    Span<byte> sbDescriptorData = _context.PhysicalMemory.Read(sbDescAddress, 0x10);
+
+                    SbDescriptor sbDescriptor = MemoryMarshal.Cast<byte, SbDescriptor>(sbDescriptorData)[0];
+
+                    _bufferManager.SetGraphicsStorageBuffer(stage, sb.Slot, sbDescriptor.PackAddress(), (uint)sbDescriptor.Size);
+                }
+
+                for (int index = 0; index < info.CBuffers.Count; index++)
+                {
+                    ubEnableMask |= 1u << info.CBuffers[index].Slot;
+                }
+
+                _bufferManager.SetGraphicsStorageBufferEnableMask(stage, sbEnableMask);
+                _bufferManager.SetGraphicsUniformBufferEnableMask(stage, ubEnableMask);
+            }
+
+            _context.Renderer.GraphicsPipeline.BindProgram(gs.Interface);
+        }
+
+        private static Target GetTarget(Shader.TextureTarget target)
+        {
+            target &= ~Shader.TextureTarget.Shadow;
+
+            switch (target)
+            {
+                case Shader.TextureTarget.Texture1D:
+                    return Target.Texture1D;
+
+                case Shader.TextureTarget.Texture1D | Shader.TextureTarget.Array:
+                    return Target.Texture1DArray;
+
+                case Shader.TextureTarget.Texture2D:
+                    return Target.Texture2D;
+
+                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Array:
+                    return Target.Texture2DArray;
+
+                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample:
+                    return Target.Texture2DMultisample;
+
+                case Shader.TextureTarget.Texture2D | Shader.TextureTarget.Multisample | Shader.TextureTarget.Array:
+                    return Target.Texture2DMultisampleArray;
+
+                case Shader.TextureTarget.Texture3D:
+                    return Target.Texture3D;
+
+                case Shader.TextureTarget.TextureCube:
+                    return Target.Cubemap;
+
+                case Shader.TextureTarget.TextureCube | Shader.TextureTarget.Array:
+                    return Target.CubemapArray;
+            }
+
+            // TODO: Warning.
+
+            return Target.Texture2D;
+        }
+
+        private void InvalidateTextures(int argument)
+        {
+            _textureManager.Flush();
+        }
+    }
+}

+ 34 - 0
Ryujinx.Graphics.Gpu/Engine/ShaderAddresses.cs

@@ -0,0 +1,34 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    struct ShaderAddresses : IEquatable<ShaderAddresses>
+    {
+        public ulong VertexA;
+        public ulong Vertex;
+        public ulong TessControl;
+        public ulong TessEvaluation;
+        public ulong Geometry;
+        public ulong Fragment;
+
+        public override bool Equals(object other)
+        {
+            return other is ShaderAddresses addresses && Equals(addresses);
+        }
+
+        public bool Equals(ShaderAddresses other)
+        {
+            return VertexA        == other.VertexA &&
+                   Vertex         == other.Vertex &&
+                   TessControl    == other.TessControl &&
+                   TessEvaluation == other.TessEvaluation &&
+                   Geometry       == other.Geometry &&
+                   Fragment       == other.Fragment;
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode.Combine(VertexA, Vertex, TessControl, TessEvaluation, Geometry, Fragment);
+        }
+    }
+}

+ 228 - 0
Ryujinx.Graphics.Gpu/Engine/ShaderCache.cs

@@ -0,0 +1,228 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Shader.Translation;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+
+namespace Ryujinx.Graphics.Gpu.Engine
+{
+    class ShaderCache
+    {
+        private const int MaxProgramSize = 0x100000;
+
+        private GpuContext _context;
+
+        private ShaderDumper _dumper;
+
+        private Dictionary<ulong, ComputeShader> _cpPrograms;
+
+        private Dictionary<ShaderAddresses, GraphicsShader> _gpPrograms;
+
+        public ShaderCache(GpuContext context)
+        {
+            _context = context;
+
+            _dumper = new ShaderDumper(context);
+
+            _cpPrograms = new Dictionary<ulong, ComputeShader>();
+
+            _gpPrograms = new Dictionary<ShaderAddresses, GraphicsShader>();
+        }
+
+        public ComputeShader GetComputeShader(ulong gpuVa, int localSizeX, int localSizeY, int localSizeZ)
+        {
+            if (!_cpPrograms.TryGetValue(gpuVa, out ComputeShader cpShader))
+            {
+                ShaderProgram shader = TranslateComputeShader(gpuVa);
+
+                shader.Replace(DefineNames.LocalSizeX, localSizeX.ToString(CultureInfo.InvariantCulture));
+                shader.Replace(DefineNames.LocalSizeY, localSizeY.ToString(CultureInfo.InvariantCulture));
+                shader.Replace(DefineNames.LocalSizeZ, localSizeZ.ToString(CultureInfo.InvariantCulture));
+
+                IShader hostShader = _context.Renderer.CompileShader(shader);
+
+                IProgram program = _context.Renderer.CreateProgram(new IShader[] { hostShader });
+
+                cpShader = new ComputeShader(program, shader);
+
+                _cpPrograms.Add(gpuVa, cpShader);
+            }
+
+            return cpShader;
+        }
+
+        public GraphicsShader GetGraphicsShader(ShaderAddresses addresses)
+        {
+            if (!_gpPrograms.TryGetValue(addresses, out GraphicsShader gpShader))
+            {
+                gpShader = new GraphicsShader();
+
+                if (addresses.VertexA != 0)
+                {
+                    gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex, addresses.VertexA);
+                }
+                else
+                {
+                    gpShader.Shader[0] = TranslateGraphicsShader(addresses.Vertex);
+                }
+
+                gpShader.Shader[1] = TranslateGraphicsShader(addresses.TessControl);
+                gpShader.Shader[2] = TranslateGraphicsShader(addresses.TessEvaluation);
+                gpShader.Shader[3] = TranslateGraphicsShader(addresses.Geometry);
+                gpShader.Shader[4] = TranslateGraphicsShader(addresses.Fragment);
+
+                BackpropQualifiers(gpShader);
+
+                List<IShader> shaders = new List<IShader>();
+
+                for (int stage = 0; stage < gpShader.Shader.Length; stage++)
+                {
+                    if (gpShader.Shader[stage] == null)
+                    {
+                        continue;
+                    }
+
+                    IShader shader = _context.Renderer.CompileShader(gpShader.Shader[stage]);
+
+                    shaders.Add(shader);
+                }
+
+                gpShader.Interface = _context.Renderer.CreateProgram(shaders.ToArray());
+
+                _gpPrograms.Add(addresses, gpShader);
+            }
+
+            return gpShader;
+        }
+
+        private ShaderProgram TranslateComputeShader(ulong gpuVa)
+        {
+            if (gpuVa == 0)
+            {
+                return null;
+            }
+
+            ShaderProgram program;
+
+            const TranslationFlags flags =
+                TranslationFlags.Compute |
+                TranslationFlags.Unspecialized;
+
+            TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
+
+            Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+            program = Translator.Translate(code, translationConfig);
+
+            _dumper.Dump(gpuVa, compute : true);
+
+            return program;
+        }
+
+        private ShaderProgram TranslateGraphicsShader(ulong gpuVa, ulong gpuVaA = 0)
+        {
+            if (gpuVa == 0)
+            {
+                return null;
+            }
+
+            ShaderProgram program;
+
+            const TranslationFlags flags =
+                TranslationFlags.DebugMode |
+                TranslationFlags.Unspecialized;
+
+            TranslationConfig translationConfig = new TranslationConfig(0x10000, _dumper.CurrentDumpIndex, flags);
+
+            if (gpuVaA != 0)
+            {
+                Span<byte> codeA = _context.MemoryAccessor.Read(gpuVaA, MaxProgramSize);
+                Span<byte> codeB = _context.MemoryAccessor.Read(gpuVa,  MaxProgramSize);
+
+                program = Translator.Translate(codeA, codeB, translationConfig);
+
+                _dumper.Dump(gpuVaA, compute: false);
+                _dumper.Dump(gpuVa,  compute: false);
+            }
+            else
+            {
+                Span<byte> code = _context.MemoryAccessor.Read(gpuVa, MaxProgramSize);
+
+                program = Translator.Translate(code, translationConfig);
+
+                _dumper.Dump(gpuVa, compute: false);
+            }
+
+            if (program.Stage == ShaderStage.Geometry)
+            {
+                PrimitiveType primitiveType = _context.Methods.PrimitiveType;
+
+                string inPrimitive = "points";
+
+                switch (primitiveType)
+                {
+                    case PrimitiveType.Points:
+                        inPrimitive = "points";
+                        break;
+                    case PrimitiveType.Lines:
+                    case PrimitiveType.LineLoop:
+                    case PrimitiveType.LineStrip:
+                        inPrimitive = "lines";
+                        break;
+                    case PrimitiveType.LinesAdjacency:
+                    case PrimitiveType.LineStripAdjacency:
+                        inPrimitive = "lines_adjacency";
+                        break;
+                    case PrimitiveType.Triangles:
+                    case PrimitiveType.TriangleStrip:
+                    case PrimitiveType.TriangleFan:
+                        inPrimitive = "triangles";
+                        break;
+                    case PrimitiveType.TrianglesAdjacency:
+                    case PrimitiveType.TriangleStripAdjacency:
+                        inPrimitive = "triangles_adjacency";
+                        break;
+                }
+
+                program.Replace(DefineNames.InputTopologyName, inPrimitive);
+            }
+
+            return program;
+        }
+
+        private void BackpropQualifiers(GraphicsShader program)
+        {
+            ShaderProgram fragmentShader = program.Shader[4];
+
+            bool isFirst = true;
+
+            for (int stage = 3; stage >= 0; stage--)
+            {
+                if (program.Shader[stage] == null)
+                {
+                    continue;
+                }
+
+                // We need to iterate backwards, since we do name replacement,
+                // and it would otherwise replace a subset of the longer names.
+                for (int attr = 31; attr >= 0; attr--)
+                {
+                    string iq = fragmentShader?.Info.InterpolationQualifiers[attr].ToGlslQualifier() ?? string.Empty;
+
+                    if (isFirst && iq != string.Empty)
+                    {
+                        program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr}", iq);
+                    }
+                    else
+                    {
+                        program.Shader[stage].Replace($"{DefineNames.OutQualifierPrefixName}{attr} ", string.Empty);
+                    }
+                }
+
+                isFirst = false;
+            }
+        }
+    }
+}

+ 38 - 44
Ryujinx.Graphics/Gal/ShaderDumper.cs → Ryujinx.Graphics.Gpu/Engine/ShaderDumper.cs

@@ -1,27 +1,43 @@
-using System;
 using System.IO;
 
-namespace Ryujinx.Graphics.Gal
+namespace Ryujinx.Graphics.Gpu.Engine
 {
-    static class ShaderDumper
+    class ShaderDumper
     {
-        private static string _runtimeDir;
+        private const int ShaderHeaderSize = 0x50;
 
-        public static int DumpIndex { get; private set; } = 1;
+        private GpuContext _context;
 
-        public static void Dump(IGalMemory memory, long position, GalShaderType type, string extSuffix = "")
+        private string _runtimeDir;
+        private string _dumpPath;
+        private int    _dumpIndex;
+
+        public int CurrentDumpIndex => _dumpIndex;
+
+        public ShaderDumper(GpuContext context)
+        {
+            _context = context;
+
+            _dumpIndex = 1;
+        }
+
+        public void Dump(ulong gpuVa, bool compute)
         {
-            if (!IsDumpEnabled())
+            _dumpPath = GraphicsConfig.ShadersDumpPath;
+
+            if (string.IsNullOrWhiteSpace(_dumpPath))
             {
                 return;
             }
 
-            string fileName = "Shader" + DumpIndex.ToString("d4") + "." + ShaderExtension(type) + extSuffix + ".bin";
+            string fileName = "Shader" + _dumpIndex.ToString("d4") + ".bin";
 
             string fullPath = Path.Combine(FullDir(), fileName);
             string codePath = Path.Combine(CodeDir(), fileName);
 
-            DumpIndex++;
+            _dumpIndex++;
+
+            ulong headerSize = compute ? 0UL : ShaderHeaderSize;
 
             using (FileStream fullFile = File.Create(fullPath))
             using (FileStream codeFile = File.Create(codePath))
@@ -29,25 +45,25 @@ namespace Ryujinx.Graphics.Gal
                 BinaryWriter fullWriter = new BinaryWriter(fullFile);
                 BinaryWriter codeWriter = new BinaryWriter(codeFile);
 
-                for (long i = 0; i < 0x50; i += 4)
+                for (ulong i = 0; i < headerSize; i += 4)
                 {
-                    fullWriter.Write(memory.ReadInt32(position + i));
+                    fullWriter.Write(_context.MemoryAccessor.ReadInt32(gpuVa + i));
                 }
 
-                long offset = 0;
+                ulong offset = 0;
 
                 ulong instruction = 0;
 
-                // Dump until a NOP instruction is found
+                // Dump until a NOP instruction is found.
                 while ((instruction >> 48 & 0xfff8) != 0x50b0)
                 {
-                    uint word0 = (uint)memory.ReadInt32(position + 0x50 + offset + 0);
-                    uint word1 = (uint)memory.ReadInt32(position + 0x50 + offset + 4);
+                    uint word0 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 0);
+                    uint word1 = (uint)_context.MemoryAccessor.ReadInt32(gpuVa + headerSize + offset + 4);
 
                     instruction = word0 | (ulong)word1 << 32;
 
                     // Zero instructions (other kind of NOP) stop immediately,
-                    // this is to avoid two rows of zeroes
+                    // this is to avoid two rows of zeroes.
                     if (instruction == 0)
                     {
                         break;
@@ -59,7 +75,7 @@ namespace Ryujinx.Graphics.Gal
                     offset += 8;
                 }
 
-                // Align to meet nvdisasm requirements
+                // Align to meet nvdisasm requirements.
                 while (offset % 0x20 != 0)
                 {
                     fullWriter.Write(0);
@@ -70,22 +86,17 @@ namespace Ryujinx.Graphics.Gal
             }
         }
 
-        public static bool IsDumpEnabled()
-        {
-            return !string.IsNullOrWhiteSpace(GraphicsConfig.ShadersDumpPath);
-        }
-
-        private static string FullDir()
+        private string FullDir()
         {
             return CreateAndReturn(Path.Combine(DumpDir(), "Full"));
         }
 
-        private static string CodeDir()
+        private string CodeDir()
         {
             return CreateAndReturn(Path.Combine(DumpDir(), "Code"));
         }
 
-        private static string DumpDir()
+        private string DumpDir()
         {
             if (string.IsNullOrEmpty(_runtimeDir))
             {
@@ -93,7 +104,7 @@ namespace Ryujinx.Graphics.Gal
 
                 do
                 {
-                    _runtimeDir = Path.Combine(GraphicsConfig.ShadersDumpPath, "Dumps" + index.ToString("d2"));
+                    _runtimeDir = Path.Combine(_dumpPath, "Dumps" + index.ToString("d2"));
 
                     index++;
                 }
@@ -107,26 +118,9 @@ namespace Ryujinx.Graphics.Gal
 
         private static string CreateAndReturn(string dir)
         {
-            if (!Directory.Exists(dir))
-            {
-                Directory.CreateDirectory(dir);
-            }
+            Directory.CreateDirectory(dir);
 
             return dir;
         }
-
-        private static string ShaderExtension(GalShaderType type)
-        {
-            switch (type)
-            {
-                case GalShaderType.Vertex:         return "vert";
-                case GalShaderType.TessControl:    return "tesc";
-                case GalShaderType.TessEvaluation: return "tese";
-                case GalShaderType.Geometry:       return "geom";
-                case GalShaderType.Fragment:       return "frag";
-
-                default: throw new ArgumentException(nameof(type));
-            }
-        }
     }
 }

+ 100 - 0
Ryujinx.Graphics.Gpu/GpuContext.cs

@@ -0,0 +1,100 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Engine;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    public class GpuContext
+    {
+        public IRenderer Renderer { get; }
+
+        internal GpuState State { get; }
+
+        internal IPhysicalMemory PhysicalMemory { get; private set; }
+
+        public MemoryManager MemoryManager { get; }
+
+        internal MemoryAccessor MemoryAccessor { get; }
+
+        internal Methods Methods { get; }
+
+        internal NvGpuFifo Fifo { get; }
+
+        public DmaPusher DmaPusher { get; }
+
+        internal int SequenceNumber { get; private set; }
+
+        private Lazy<Capabilities> _caps;
+
+        internal Capabilities Capabilities => _caps.Value;
+
+        public GpuContext(IRenderer renderer)
+        {
+            Renderer = renderer;
+
+            State = new GpuState();
+
+            MemoryManager = new MemoryManager();
+
+            MemoryAccessor = new MemoryAccessor(this);
+
+            Methods = new Methods(this);
+
+            Fifo = new NvGpuFifo(this);
+
+            DmaPusher = new DmaPusher(this);
+
+            _caps = new Lazy<Capabilities>(GetCapabilities);
+        }
+
+        internal void AdvanceSequence()
+        {
+            SequenceNumber++;
+        }
+
+        public ITexture GetTexture(
+            ulong  address,
+            int    width,
+            int    height,
+            int    stride,
+            bool   isLinear,
+            int    gobBlocksInY,
+            Format format,
+            int    bytesPerPixel)
+        {
+            FormatInfo formatInfo = new FormatInfo(format, 1, 1, bytesPerPixel);
+
+            TextureInfo info = new TextureInfo(
+                address,
+                width,
+                height,
+                1,
+                1,
+                1,
+                1,
+                stride,
+                isLinear,
+                gobBlocksInY,
+                1,
+                1,
+                Target.Texture2D,
+                formatInfo);
+
+            return Methods.GetTexture(address)?.HostTexture;
+        }
+
+        private Capabilities GetCapabilities()
+        {
+            return Renderer.GetCapabilities();
+        }
+
+        public void SetVmm(IPhysicalMemory mm)
+        {
+            PhysicalMemory = mm;
+        }
+    }
+}

+ 12 - 0
Ryujinx.Graphics.Gpu/GraphicsConfig.cs

@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    public static class GraphicsConfig
+    {
+        public static string ShadersDumpPath;
+
+        public static bool FastGpuTime = true;
+
+        public static bool DisableTUpdate;
+        public static bool DisableBUpdate;
+    }
+}

+ 62 - 0
Ryujinx.Graphics.Gpu/Image/AutoDeleteCache.cs

@@ -0,0 +1,62 @@
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class AutoDeleteCache : IEnumerable<Texture>
+    {
+        private const int MaxCapacity = 2048;
+
+        private LinkedList<Texture> _textures;
+
+        public AutoDeleteCache()
+        {
+            _textures = new LinkedList<Texture>();
+        }
+
+        public void Add(Texture texture)
+        {
+            texture.IncrementReferenceCount();
+
+            texture.CacheNode = _textures.AddLast(texture);
+
+            if (_textures.Count > MaxCapacity)
+            {
+                Texture oldestTexture = _textures.First.Value;
+
+                _textures.RemoveFirst();
+
+                oldestTexture.DecrementReferenceCount();
+
+                oldestTexture.CacheNode = null;
+            }
+        }
+
+        public void Lift(Texture texture)
+        {
+            if (texture.CacheNode != null)
+            {
+                if (texture.CacheNode != _textures.Last)
+                {
+                    _textures.Remove(texture.CacheNode);
+
+                    texture.CacheNode = _textures.AddLast(texture);
+                }
+            }
+            else
+            {
+                Add(texture);
+            }
+        }
+
+        public IEnumerator<Texture> GetEnumerator()
+        {
+            return _textures.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return _textures.GetEnumerator();
+        }
+    }
+}

+ 31 - 0
Ryujinx.Graphics.Gpu/Image/FormatInfo.cs

@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    struct FormatInfo
+    {
+        private static FormatInfo _rgba8 = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+
+        public static FormatInfo Default => _rgba8;
+
+        public Format Format { get; }
+
+        public int BlockWidth    { get; }
+        public int BlockHeight   { get; }
+        public int BytesPerPixel { get; }
+
+        public bool IsCompressed => (BlockWidth | BlockHeight) != 1;
+
+        public FormatInfo(
+            Format format,
+            int    blockWidth,
+            int    blockHeight,
+            int    bytesPerPixel)
+        {
+            Format        = format;
+            BlockWidth    = blockWidth;
+            BlockHeight   = blockHeight;
+            BytesPerPixel = bytesPerPixel;
+        }
+    }
+}

+ 201 - 0
Ryujinx.Graphics.Gpu/Image/FormatTable.cs

@@ -0,0 +1,201 @@
+using Ryujinx.Graphics.GAL;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    static class FormatTable
+    {
+        private static Dictionary<uint, FormatInfo> _textureFormats = new Dictionary<uint, FormatInfo>()
+        {
+            { 0x2491d, new FormatInfo(Format.R8Unorm,           1,  1,  1)  },
+            { 0x1249d, new FormatInfo(Format.R8Snorm,           1,  1,  1)  },
+            { 0x4921d, new FormatInfo(Format.R8Uint,            1,  1,  1)  },
+            { 0x36d9d, new FormatInfo(Format.R8Sint,            1,  1,  1)  },
+            { 0x7ff9b, new FormatInfo(Format.R16Float,          1,  1,  2)  },
+            { 0x2491b, new FormatInfo(Format.R16Unorm,          1,  1,  2)  },
+            { 0x1249b, new FormatInfo(Format.R16Snorm,          1,  1,  2)  },
+            { 0x4921b, new FormatInfo(Format.R16Uint,           1,  1,  2)  },
+            { 0x36d9b, new FormatInfo(Format.R16Sint,           1,  1,  2)  },
+            { 0x7ff8f, new FormatInfo(Format.R32Float,          1,  1,  4)  },
+            { 0x4920f, new FormatInfo(Format.R32Uint,           1,  1,  4)  },
+            { 0x36d8f, new FormatInfo(Format.R32Sint,           1,  1,  4)  },
+            { 0x24918, new FormatInfo(Format.R8G8Unorm,         1,  1,  2)  },
+            { 0x12498, new FormatInfo(Format.R8G8Snorm,         1,  1,  2)  },
+            { 0x49218, new FormatInfo(Format.R8G8Uint,          1,  1,  2)  },
+            { 0x36d98, new FormatInfo(Format.R8G8Sint,          1,  1,  2)  },
+            { 0x7ff8c, new FormatInfo(Format.R16G16Float,       1,  1,  4)  },
+            { 0x2490c, new FormatInfo(Format.R16G16Unorm,       1,  1,  4)  },
+            { 0x1248c, new FormatInfo(Format.R16G16Snorm,       1,  1,  4)  },
+            { 0x4920c, new FormatInfo(Format.R16G16Uint,        1,  1,  4)  },
+            { 0x36d8c, new FormatInfo(Format.R16G16Sint,        1,  1,  4)  },
+            { 0x7ff84, new FormatInfo(Format.R32G32Float,       1,  1,  8)  },
+            { 0x49204, new FormatInfo(Format.R32G32Uint,        1,  1,  8)  },
+            { 0x36d84, new FormatInfo(Format.R32G32Sint,        1,  1,  8)  },
+            { 0x7ff82, new FormatInfo(Format.R32G32B32Float,    1,  1,  12) },
+            { 0x49202, new FormatInfo(Format.R32G32B32Uint,     1,  1,  12) },
+            { 0x36d82, new FormatInfo(Format.R32G32B32Sint,     1,  1,  12) },
+            { 0x24908, new FormatInfo(Format.R8G8B8A8Unorm,     1,  1,  4)  },
+            { 0x12488, new FormatInfo(Format.R8G8B8A8Snorm,     1,  1,  4)  },
+            { 0x49208, new FormatInfo(Format.R8G8B8A8Uint,      1,  1,  4)  },
+            { 0x36d88, new FormatInfo(Format.R8G8B8A8Sint,      1,  1,  4)  },
+            { 0x7ff83, new FormatInfo(Format.R16G16B16A16Float, 1,  1,  8)  },
+            { 0x24903, new FormatInfo(Format.R16G16B16A16Unorm, 1,  1,  8)  },
+            { 0x12483, new FormatInfo(Format.R16G16B16A16Snorm, 1,  1,  8)  },
+            { 0x49203, new FormatInfo(Format.R16G16B16A16Uint,  1,  1,  8)  },
+            { 0x36d83, new FormatInfo(Format.R16G16B16A16Sint,  1,  1,  8)  },
+            { 0x7ff81, new FormatInfo(Format.R32G32B32A32Float, 1,  1,  16) },
+            { 0x49201, new FormatInfo(Format.R32G32B32A32Uint,  1,  1,  16) },
+            { 0x36d81, new FormatInfo(Format.R32G32B32A32Sint,  1,  1,  16) },
+            { 0x2493a, new FormatInfo(Format.D16Unorm,          1,  1,  2)  },
+            { 0x7ffaf, new FormatInfo(Format.D32Float,          1,  1,  4)  },
+            { 0x24a29, new FormatInfo(Format.D24UnormS8Uint,    1,  1,  4)  },
+            { 0x253b0, new FormatInfo(Format.D32FloatS8Uint,    1,  1,  8)  },
+            { 0xa4908, new FormatInfo(Format.R8G8B8A8Srgb,      1,  1,  4)  },
+            { 0x24912, new FormatInfo(Format.R4G4B4A4Unorm,     1,  1,  2)  },
+            { 0x24914, new FormatInfo(Format.R5G5B5A1Unorm,     1,  1,  2)  },
+            { 0x24915, new FormatInfo(Format.R5G6B5Unorm,       1,  1,  2)  },
+            { 0x24909, new FormatInfo(Format.R10G10B10A2Unorm,  1,  1,  4)  },
+            { 0x49209, new FormatInfo(Format.R10G10B10A2Uint,   1,  1,  4)  },
+            { 0x7ffa1, new FormatInfo(Format.R11G11B10Float,    1,  1,  4)  },
+            { 0x7ffa0, new FormatInfo(Format.R9G9B9E5Float,     1,  1,  4)  },
+            { 0x24924, new FormatInfo(Format.Bc1RgbaUnorm,      4,  4,  8)  },
+            { 0x24925, new FormatInfo(Format.Bc2Unorm,          4,  4,  16) },
+            { 0x24926, new FormatInfo(Format.Bc3Unorm,          4,  4,  16) },
+            { 0xa4924, new FormatInfo(Format.Bc1RgbaSrgb,       4,  4,  8)  },
+            { 0xa4925, new FormatInfo(Format.Bc2Srgb,           4,  4,  16) },
+            { 0xa4926, new FormatInfo(Format.Bc3Srgb,           4,  4,  16) },
+            { 0x24927, new FormatInfo(Format.Bc4Unorm,          4,  4,  8)  },
+            { 0x124a7, new FormatInfo(Format.Bc4Snorm,          4,  4,  8)  },
+            { 0x24928, new FormatInfo(Format.Bc5Unorm,          4,  4,  16) },
+            { 0x124a8, new FormatInfo(Format.Bc5Snorm,          4,  4,  16) },
+            { 0x24917, new FormatInfo(Format.Bc7Unorm,          4,  4,  16) },
+            { 0xa4917, new FormatInfo(Format.Bc7Srgb,           4,  4,  16) },
+            { 0x7ff90, new FormatInfo(Format.Bc6HUfloat,        4,  4,  16) },
+            { 0x7ff91, new FormatInfo(Format.Bc6HSfloat,        4,  4,  16) },
+            { 0x24940, new FormatInfo(Format.Astc4x4Unorm,      4,  4,  16) },
+            { 0x24950, new FormatInfo(Format.Astc5x4Unorm,      5,  4,  16) },
+            { 0x24941, new FormatInfo(Format.Astc5x5Unorm,      5,  5,  16) },
+            { 0x24951, new FormatInfo(Format.Astc6x5Unorm,      6,  5,  16) },
+            { 0x24942, new FormatInfo(Format.Astc6x6Unorm,      6,  6,  16) },
+            { 0x24955, new FormatInfo(Format.Astc8x5Unorm,      8,  5,  16) },
+            { 0x24952, new FormatInfo(Format.Astc8x6Unorm,      8,  6,  16) },
+            { 0x24944, new FormatInfo(Format.Astc8x8Unorm,      8,  8,  16) },
+            { 0x24956, new FormatInfo(Format.Astc10x5Unorm,     10, 5,  16) },
+            { 0x24957, new FormatInfo(Format.Astc10x6Unorm,     10, 6,  16) },
+            { 0x24953, new FormatInfo(Format.Astc10x8Unorm,     10, 8,  16) },
+            { 0x24945, new FormatInfo(Format.Astc10x10Unorm,    10, 10, 16) },
+            { 0x24954, new FormatInfo(Format.Astc12x10Unorm,    12, 10, 16) },
+            { 0x24946, new FormatInfo(Format.Astc12x12Unorm,    12, 12, 16) },
+            { 0xa4940, new FormatInfo(Format.Astc4x4Srgb,       4,  4,  16) },
+            { 0xa4950, new FormatInfo(Format.Astc5x4Srgb,       5,  4,  16) },
+            { 0xa4941, new FormatInfo(Format.Astc5x5Srgb,       5,  5,  16) },
+            { 0xa4951, new FormatInfo(Format.Astc6x5Srgb,       6,  5,  16) },
+            { 0xa4942, new FormatInfo(Format.Astc6x6Srgb,       6,  6,  16) },
+            { 0xa4955, new FormatInfo(Format.Astc8x5Srgb,       8,  5,  16) },
+            { 0xa4952, new FormatInfo(Format.Astc8x6Srgb,       8,  6,  16) },
+            { 0xa4944, new FormatInfo(Format.Astc8x8Srgb,       8,  8,  16) },
+            { 0xa4956, new FormatInfo(Format.Astc10x5Srgb,      10, 5,  16) },
+            { 0xa4957, new FormatInfo(Format.Astc10x6Srgb,      10, 6,  16) },
+            { 0xa4953, new FormatInfo(Format.Astc10x8Srgb,      10, 8,  16) },
+            { 0xa4945, new FormatInfo(Format.Astc10x10Srgb,     10, 10, 16) },
+            { 0xa4954, new FormatInfo(Format.Astc12x10Srgb,     12, 10, 16) },
+            { 0xa4946, new FormatInfo(Format.Astc12x12Srgb,     12, 12, 16) },
+            { 0x24913, new FormatInfo(Format.A1B5G5R5Unorm,     1,  1,  2)  }
+        };
+
+        private static Dictionary<ulong, Format> _attribFormats = new Dictionary<ulong, Format>()
+        {
+            { 0x13a00000, Format.R8Unorm             },
+            { 0x0ba00000, Format.R8Snorm             },
+            { 0x23a00000, Format.R8Uint              },
+            { 0x1ba00000, Format.R8Sint              },
+            { 0x3b600000, Format.R16Float            },
+            { 0x13600000, Format.R16Unorm            },
+            { 0x0b600000, Format.R16Snorm            },
+            { 0x23600000, Format.R16Uint             },
+            { 0x1b600000, Format.R16Sint             },
+            { 0x3a400000, Format.R32Float            },
+            { 0x22400000, Format.R32Uint             },
+            { 0x1a400000, Format.R32Sint             },
+            { 0x13000000, Format.R8G8Unorm           },
+            { 0x0b000000, Format.R8G8Snorm           },
+            { 0x23000000, Format.R8G8Uint            },
+            { 0x1b000000, Format.R8G8Sint            },
+            { 0x39e00000, Format.R16G16Float         },
+            { 0x11e00000, Format.R16G16Unorm         },
+            { 0x09e00000, Format.R16G16Snorm         },
+            { 0x21e00000, Format.R16G16Uint          },
+            { 0x19e00000, Format.R16G16Sint          },
+            { 0x38800000, Format.R32G32Float         },
+            { 0x20800000, Format.R32G32Uint          },
+            { 0x18800000, Format.R32G32Sint          },
+            { 0x12600000, Format.R8G8B8Unorm         },
+            { 0x0a600000, Format.R8G8B8Snorm         },
+            { 0x22600000, Format.R8G8B8Uint          },
+            { 0x1a600000, Format.R8G8B8Sint          },
+            { 0x38a00000, Format.R16G16B16Float      },
+            { 0x10a00000, Format.R16G16B16Unorm      },
+            { 0x08a00000, Format.R16G16B16Snorm      },
+            { 0x20a00000, Format.R16G16B16Uint       },
+            { 0x18a00000, Format.R16G16B16Sint       },
+            { 0x38400000, Format.R32G32B32Float      },
+            { 0x20400000, Format.R32G32B32Uint       },
+            { 0x18400000, Format.R32G32B32Sint       },
+            { 0x11400000, Format.R8G8B8A8Unorm       },
+            { 0x09400000, Format.R8G8B8A8Snorm       },
+            { 0x21400000, Format.R8G8B8A8Uint        },
+            { 0x19400000, Format.R8G8B8A8Sint        },
+            { 0x38600000, Format.R16G16B16A16Float   },
+            { 0x10600000, Format.R16G16B16A16Unorm   },
+            { 0x08600000, Format.R16G16B16A16Snorm   },
+            { 0x20600000, Format.R16G16B16A16Uint    },
+            { 0x18600000, Format.R16G16B16A16Sint    },
+            { 0x38200000, Format.R32G32B32A32Float   },
+            { 0x20200000, Format.R32G32B32A32Uint    },
+            { 0x18200000, Format.R32G32B32A32Sint    },
+            { 0x16000000, Format.R10G10B10A2Unorm    },
+            { 0x26000000, Format.R10G10B10A2Uint     },
+            { 0x3e200000, Format.R11G11B10Float      },
+            { 0x2ba00000, Format.R8Uscaled           },
+            { 0x33a00000, Format.R8Sscaled           },
+            { 0x2b600000, Format.R16Uscaled          },
+            { 0x33600000, Format.R16Sscaled          },
+            { 0x2a400000, Format.R32Uscaled          },
+            { 0x32400000, Format.R32Sscaled          },
+            { 0x2b000000, Format.R8G8Uscaled         },
+            { 0x33000000, Format.R8G8Sscaled         },
+            { 0x29e00000, Format.R16G16Uscaled       },
+            { 0x31e00000, Format.R16G16Sscaled       },
+            { 0x28800000, Format.R32G32Uscaled       },
+            { 0x30800000, Format.R32G32Sscaled       },
+            { 0x2a600000, Format.R8G8B8Uscaled       },
+            { 0x32600000, Format.R8G8B8Sscaled       },
+            { 0x28a00000, Format.R16G16B16Uscaled    },
+            { 0x30a00000, Format.R16G16B16Sscaled    },
+            { 0x28400000, Format.R32G32B32Uscaled    },
+            { 0x30400000, Format.R32G32B32Sscaled    },
+            { 0x29400000, Format.R8G8B8A8Uscaled     },
+            { 0x31400000, Format.R8G8B8A8Sscaled     },
+            { 0x28600000, Format.R16G16B16A16Uscaled },
+            { 0x30600000, Format.R16G16B16A16Sscaled },
+            { 0x28200000, Format.R32G32B32A32Uscaled },
+            { 0x30200000, Format.R32G32B32A32Sscaled },
+            { 0x0e000000, Format.R10G10B10A2Snorm    },
+            { 0x1e000000, Format.R10G10B10A2Sint     },
+            { 0x2e000000, Format.R10G10B10A2Uscaled  },
+            { 0x36000000, Format.R10G10B10A2Sscaled  }
+        };
+
+        public static bool TryGetTextureFormat(uint encoded, bool isSrgb, out FormatInfo format)
+        {
+            encoded |= (isSrgb ? 1u << 19 : 0u);
+
+            return _textureFormats.TryGetValue(encoded, out format);
+        }
+
+        public static bool TryGetAttribFormat(uint encoded, out Format format)
+        {
+            return _attribFormats.TryGetValue(encoded, out format);
+        }
+    }
+}

+ 99 - 0
Ryujinx.Graphics.Gpu/Image/Pool.cs

@@ -0,0 +1,99 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    abstract class Pool<T> : IDisposable
+    {
+        protected const int DescriptorSize = 0x20;
+
+        protected GpuContext Context;
+
+        protected T[] Items;
+
+        public ulong Address { get; }
+        public ulong Size    { get; }
+
+        public Pool(GpuContext context, ulong address, int maximumId)
+        {
+            Context = context;
+
+            int count = maximumId + 1;
+
+            ulong size = (ulong)(uint)count * DescriptorSize;;
+
+            Items = new T[count];
+
+            Address = address;
+            Size    = size;
+        }
+
+        public abstract T Get(int id);
+
+        public void SynchronizeMemory()
+        {
+            (ulong, ulong)[] modifiedRanges = Context.PhysicalMemory.GetModifiedRanges(Address, Size);
+
+            for (int index = 0; index < modifiedRanges.Length; index++)
+            {
+                (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+                if (mAddress < Address)
+                {
+                    mAddress = Address;
+                }
+
+                ulong maxSize = Address + Size - mAddress;
+
+                if (mSize > maxSize)
+                {
+                    mSize = maxSize;
+                }
+
+                InvalidateRangeImpl(mAddress, mSize);
+            }
+        }
+
+        public void InvalidateRange(ulong address, ulong size)
+        {
+            ulong endAddress = address + size;
+
+            ulong texturePoolEndAddress = Address + Size;
+
+            // If the range being invalidated is not overlapping the texture pool range,
+            // then we don't have anything to do, exit early.
+            if (address >= texturePoolEndAddress || endAddress <= Address)
+            {
+                return;
+            }
+
+            if (address < Address)
+            {
+                address = Address;
+            }
+
+            if (endAddress > texturePoolEndAddress)
+            {
+                endAddress = texturePoolEndAddress;
+            }
+
+            InvalidateRangeImpl(address, size);
+        }
+
+        protected abstract void InvalidateRangeImpl(ulong address, ulong size);
+
+        protected abstract void Delete(T item);
+
+        public void Dispose()
+        {
+            if (Items != null)
+            {
+                for (int index = 0; index < Items.Length; index++)
+                {
+                    Delete(Items[index]);
+                }
+
+                Items = null;
+            }
+        }
+    }
+}

+ 9 - 0
Ryujinx.Graphics.Gpu/Image/ReductionFilter.cs

@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    enum ReductionFilter
+    {
+        Average,
+        Minimum,
+        Maximum
+    }
+}

+ 52 - 0
Ryujinx.Graphics.Gpu/Image/Sampler.cs

@@ -0,0 +1,52 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Color;
+using Ryujinx.Graphics.GAL.Sampler;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class Sampler : IDisposable
+    {
+        public ISampler HostSampler { get; }
+
+        public Sampler(GpuContext context, SamplerDescriptor descriptor)
+        {
+            MinFilter minFilter = descriptor.UnpackMinFilter();
+            MagFilter magFilter = descriptor.UnpackMagFilter();
+
+            AddressMode addressU = descriptor.UnpackAddressU();
+            AddressMode addressV = descriptor.UnpackAddressV();
+            AddressMode addressP = descriptor.UnpackAddressP();
+
+            CompareMode compareMode = descriptor.UnpackCompareMode();
+            CompareOp   compareOp   = descriptor.UnpackCompareOp();
+
+            ColorF color = new ColorF(0, 0, 0, 0);
+
+            float minLod     = descriptor.UnpackMinLod();
+            float maxLod     = descriptor.UnpackMaxLod();
+            float mipLodBias = descriptor.UnpackMipLodBias();
+
+            float maxAnisotropy = descriptor.UnpackMaxAnisotropy();
+
+            HostSampler = context.Renderer.CreateSampler(new SamplerCreateInfo(
+                minFilter,
+                magFilter,
+                addressU,
+                addressV,
+                addressP,
+                compareMode,
+                compareOp,
+                color,
+                minLod,
+                maxLod,
+                mipLodBias,
+                maxAnisotropy));
+        }
+
+        public void Dispose()
+        {
+            HostSampler.Dispose();
+        }
+    }
+}

+ 132 - 0
Ryujinx.Graphics.Gpu/Image/SamplerDescriptor.cs

@@ -0,0 +1,132 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Sampler;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    struct SamplerDescriptor
+    {
+        private static readonly float[] _f5ToF32ConversionLut = new float[]
+        {
+            0.0f,
+            0.055555556f,
+            0.1f,
+            0.13636364f,
+            0.16666667f,
+            0.1923077f,
+            0.21428572f,
+            0.23333333f,
+            0.25f,
+            0.2777778f,
+            0.3f,
+            0.3181818f,
+            0.33333334f,
+            0.34615386f,
+            0.35714287f,
+            0.36666667f,
+            0.375f,
+            0.3888889f,
+            0.4f,
+            0.4090909f,
+            0.41666666f,
+            0.42307693f,
+            0.42857143f,
+            0.43333334f,
+            0.4375f,
+            0.44444445f,
+            0.45f,
+            0.45454547f,
+            0.45833334f,
+            0.46153846f,
+            0.4642857f,
+            0.46666667f
+        };
+
+        private static readonly float[] _maxAnisotropyLut = new float[]
+        {
+            1, 2, 4, 6, 8, 10, 12, 16
+        };
+
+        private const float Frac8ToF32 = 1.0f / 256.0f;
+
+        public uint Word0;
+        public uint Word1;
+        public uint Word2;
+        public uint Word3;
+        public uint BorderColorR;
+        public uint BorderColorG;
+        public uint BorderColorB;
+        public uint BorderColorA;
+
+        public AddressMode UnpackAddressU()
+        {
+            return (AddressMode)(Word0 & 7);
+        }
+
+        public AddressMode UnpackAddressV()
+        {
+            return (AddressMode)((Word0 >> 3) & 7);
+        }
+
+        public AddressMode UnpackAddressP()
+        {
+            return (AddressMode)((Word0 >> 6) & 7);
+        }
+
+        public CompareMode UnpackCompareMode()
+        {
+            return (CompareMode)((Word0 >> 9) & 1);
+        }
+
+        public CompareOp UnpackCompareOp()
+        {
+            return (CompareOp)(((Word0 >> 10) & 7) + 1);
+        }
+
+        public float UnpackMaxAnisotropy()
+        {
+            return _maxAnisotropyLut[(Word0 >> 20) & 7];
+        }
+
+        public MagFilter UnpackMagFilter()
+        {
+            return (MagFilter)(Word1 & 3);
+        }
+
+        public MinFilter UnpackMinFilter()
+        {
+            int minFilter = (int)(Word1 >> 4) & 3;
+            int mipFilter = (int)(Word1 >> 6) & 3;
+
+            return (MinFilter)(minFilter + (mipFilter - 1) * 2);
+        }
+
+        public ReductionFilter UnpackReductionFilter()
+        {
+            return (ReductionFilter)((Word1 >> 10) & 3);
+        }
+
+        public float UnpackMipLodBias()
+        {
+            int fixedValue = (int)(Word1 >> 12) & 0x1fff;
+
+            fixedValue = (fixedValue << 19) >> 19;
+
+            return fixedValue * Frac8ToF32;
+        }
+
+        public float UnpackLodSnap()
+        {
+            return _f5ToF32ConversionLut[(Word1 >> 26) & 0x1f];
+        }
+
+        public float UnpackMinLod()
+        {
+            return (Word2 & 0xfff) * Frac8ToF32;
+        }
+
+        public float UnpackMaxLod()
+        {
+            return ((Word2 >> 12) & 0xfff) * Frac8ToF32;
+        }
+    }
+}

+ 61 - 0
Ryujinx.Graphics.Gpu/Image/SamplerPool.cs

@@ -0,0 +1,61 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class SamplerPool : Pool<Sampler>
+    {
+        public SamplerPool(GpuContext context, ulong address, int maximumId) : base(context, address, maximumId) { }
+
+        public override Sampler Get(int id)
+        {
+            if ((uint)id >= Items.Length)
+            {
+                return null;
+            }
+
+            SynchronizeMemory();
+
+            Sampler sampler = Items[id];
+
+            if (sampler == null)
+            {
+                ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+                Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+                SamplerDescriptor descriptor = MemoryMarshal.Cast<byte, SamplerDescriptor>(data)[0];
+
+                sampler = new Sampler(Context, descriptor);
+
+                Items[id] = sampler;
+            }
+
+            return sampler;
+        }
+
+        protected override void InvalidateRangeImpl(ulong address, ulong size)
+        {
+            ulong endAddress = address + size;
+
+            for (; address < endAddress; address += DescriptorSize)
+            {
+                int id = (int)((address - Address) / DescriptorSize);
+
+                Sampler sampler = Items[id];
+
+                if (sampler != null)
+                {
+                    sampler.Dispose();
+
+                    Items[id] = null;
+                }
+            }
+        }
+
+        protected override void Delete(Sampler item)
+        {
+            item?.Dispose();
+        }
+    }
+}

+ 719 - 0
Ryujinx.Graphics.Gpu/Image/Texture.cs

@@ -0,0 +1,719 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Texture;
+using Ryujinx.Graphics.Texture.Astc;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class Texture : IRange<Texture>
+    {
+        private GpuContext _context;
+
+        private TextureInfo _info;
+
+        private SizeInfo _sizeInfo;
+
+        public Format Format => _info.FormatInfo.Format;
+
+        public TextureInfo Info => _info;
+
+        private int _depth;
+        private int _layers;
+        private int _firstLayer;
+        private int _firstLevel;
+
+        private bool _hasData;
+
+        private ITexture _arrayViewTexture;
+        private Target   _arrayViewTarget;
+
+        private Texture _viewStorage;
+
+        private List<Texture> _views;
+
+        public ITexture HostTexture { get; private set; }
+
+        public LinkedListNode<Texture> CacheNode { get; set; }
+
+        public bool Modified { get; set; }
+
+        public ulong Address    => _info.Address;
+        public ulong EndAddress => _info.Address + Size;
+
+        public ulong Size => (ulong)_sizeInfo.TotalSize;
+
+        private int _referenceCount;
+
+        private int _sequenceNumber;
+
+        private Texture(
+            GpuContext  context,
+            TextureInfo info,
+            SizeInfo    sizeInfo,
+            int         firstLayer,
+            int         firstLevel)
+        {
+            InitializeTexture(context, info, sizeInfo);
+
+            _firstLayer = firstLayer;
+            _firstLevel = firstLevel;
+
+            _hasData = true;
+        }
+
+        public Texture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+        {
+            InitializeTexture(context, info, sizeInfo);
+
+            TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, context.Capabilities);
+
+            HostTexture = _context.Renderer.CreateTexture(createInfo);
+        }
+
+        private void InitializeTexture(GpuContext context, TextureInfo info, SizeInfo sizeInfo)
+        {
+            _context  = context;
+            _sizeInfo = sizeInfo;
+
+            SetInfo(info);
+
+            _viewStorage = this;
+
+            _views = new List<Texture>();
+        }
+
+        public Texture CreateView(TextureInfo info, SizeInfo sizeInfo, int firstLayer, int firstLevel)
+        {
+            Texture texture = new Texture(
+                _context,
+                info,
+                sizeInfo,
+                _firstLayer + firstLayer,
+                _firstLevel + firstLevel);
+
+            TextureCreateInfo createInfo = TextureManager.GetCreateInfo(info, _context.Capabilities);
+
+            texture.HostTexture = HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+            _viewStorage.AddView(texture);
+
+            return texture;
+        }
+
+        private void AddView(Texture texture)
+        {
+            _views.Add(texture);
+
+            texture._viewStorage = this;
+        }
+
+        private void RemoveView(Texture texture)
+        {
+            _views.Remove(texture);
+
+            texture._viewStorage = null;
+        }
+
+        public void ChangeSize(int width, int height, int depthOrLayers)
+        {
+            width  <<= _firstLevel;
+            height <<= _firstLevel;
+
+            if (_info.Target == Target.Texture3D)
+            {
+                depthOrLayers <<= _firstLevel;
+            }
+            else
+            {
+                depthOrLayers = _viewStorage._info.DepthOrLayers;
+            }
+
+            _viewStorage.RecreateStorageOrView(width, height, depthOrLayers);
+
+            foreach (Texture view in _viewStorage._views)
+            {
+                int viewWidth  = Math.Max(1, width  >> view._firstLevel);
+                int viewHeight = Math.Max(1, height >> view._firstLevel);
+
+                int viewDepthOrLayers;
+
+                if (view._info.Target == Target.Texture3D)
+                {
+                    viewDepthOrLayers = Math.Max(1, depthOrLayers >> view._firstLevel);
+                }
+                else
+                {
+                    viewDepthOrLayers = view._info.DepthOrLayers;
+                }
+
+                view.RecreateStorageOrView(viewWidth, viewHeight, viewDepthOrLayers);
+            }
+        }
+
+        private void RecreateStorageOrView(int width, int height, int depthOrLayers)
+        {
+            SetInfo(new TextureInfo(
+                _info.Address,
+                width,
+                height,
+                depthOrLayers,
+                _info.Levels,
+                _info.SamplesInX,
+                _info.SamplesInY,
+                _info.Stride,
+                _info.IsLinear,
+                _info.GobBlocksInY,
+                _info.GobBlocksInZ,
+                _info.GobBlocksInTileX,
+                _info.Target,
+                _info.FormatInfo,
+                _info.DepthStencilMode,
+                _info.SwizzleR,
+                _info.SwizzleG,
+                _info.SwizzleB,
+                _info.SwizzleA));
+
+            TextureCreateInfo createInfo = TextureManager.GetCreateInfo(_info, _context.Capabilities);
+
+            if (_viewStorage != this)
+            {
+                ReplaceStorage(_viewStorage.HostTexture.CreateView(createInfo, _firstLayer, _firstLevel));
+            }
+            else
+            {
+                ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
+
+                HostTexture.CopyTo(newStorage);
+
+                ReplaceStorage(newStorage);
+            }
+        }
+
+        public void SynchronizeMemory()
+        {
+            if (_sequenceNumber == _context.SequenceNumber && _hasData)
+            {
+                return;
+            }
+
+            _sequenceNumber = _context.SequenceNumber;
+
+            bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size).Length != 0;
+
+            if (!modified && _hasData)
+            {
+                return;
+            }
+
+            ulong pageSize = (uint)_context.PhysicalMemory.GetPageSize();
+
+            ulong pageMask = pageSize - 1;
+
+            ulong rangeAddress = Address & ~pageMask;
+
+            ulong rangeSize = (EndAddress - Address + pageMask) & ~pageMask;
+
+            _context.Methods.InvalidateRange(rangeAddress, rangeSize);
+
+            Span<byte> data = _context.PhysicalMemory.Read(Address, Size);
+
+            if (_info.IsLinear)
+            {
+                data = LayoutConverter.ConvertLinearStridedToLinear(
+                    _info.Width,
+                    _info.Height,
+                    _info.FormatInfo.BlockWidth,
+                    _info.FormatInfo.BlockHeight,
+                    _info.Stride,
+                    _info.FormatInfo.BytesPerPixel,
+                    data);
+            }
+            else
+            {
+                data = LayoutConverter.ConvertBlockLinearToLinear(
+                    _info.Width,
+                    _info.Height,
+                    _depth,
+                    _info.Levels,
+                    _layers,
+                    _info.FormatInfo.BlockWidth,
+                    _info.FormatInfo.BlockHeight,
+                    _info.FormatInfo.BytesPerPixel,
+                    _info.GobBlocksInY,
+                    _info.GobBlocksInZ,
+                    _info.GobBlocksInTileX,
+                    _sizeInfo,
+                    data);
+            }
+
+            if (!_context.Capabilities.SupportsAstcCompression && _info.FormatInfo.Format.IsAstc())
+            {
+                int blockWidth  = _info.FormatInfo.BlockWidth;
+                int blockHeight = _info.FormatInfo.BlockHeight;
+
+                data = AstcDecoder.DecodeToRgba8(
+                    data,
+                    blockWidth,
+                    blockHeight,
+                    1,
+                    _info.Width,
+                    _info.Height,
+                    _depth);
+            }
+
+            HostTexture.SetData(data);
+
+            _hasData = true;
+        }
+
+        public void Flush()
+        {
+            byte[] data = HostTexture.GetData(0);
+
+            _context.PhysicalMemory.Write(Address, data);
+        }
+
+        public bool IsPerfectMatch(TextureInfo info, TextureSearchFlags flags)
+        {
+            if (!FormatMatches(info, (flags & TextureSearchFlags.Strict) != 0))
+            {
+                return false;
+            }
+
+            if (!LayoutMatches(info))
+            {
+                return false;
+            }
+
+            if (!SizeMatches(info, (flags & TextureSearchFlags.Strict) == 0))
+            {
+                return false;
+            }
+
+            if ((flags & TextureSearchFlags.Sampler) != 0)
+            {
+                if (!SamplerParamsMatches(info))
+                {
+                    return false;
+                }
+            }
+
+            if ((flags & TextureSearchFlags.IgnoreMs) != 0)
+            {
+                bool msTargetCompatible = _info.Target == Target.Texture2DMultisample &&
+                                           info.Target == Target.Texture2D;
+
+                if (!msTargetCompatible && !TargetAndSamplesCompatible(info))
+                {
+                    return false;
+                }
+            }
+            else if (!TargetAndSamplesCompatible(info))
+            {
+                return false;
+            }
+
+            return _info.Address == info.Address && _info.Levels == info.Levels;
+        }
+
+        private bool FormatMatches(TextureInfo info, bool strict)
+        {
+            // D32F and R32F texture have the same representation internally,
+            // however the R32F format is used to sample from depth textures.
+            if (_info.FormatInfo.Format == Format.D32Float &&
+                 info.FormatInfo.Format == Format.R32Float && !strict)
+            {
+                return true;
+            }
+
+            if (_info.FormatInfo.Format == Format.R8G8B8A8Srgb &&
+                 info.FormatInfo.Format == Format.R8G8B8A8Unorm && !strict)
+            {
+                return true;
+            }
+
+            if (_info.FormatInfo.Format == Format.R8G8B8A8Unorm &&
+                 info.FormatInfo.Format == Format.R8G8B8A8Srgb && !strict)
+            {
+                return true;
+            }
+
+            return _info.FormatInfo.Format == info.FormatInfo.Format;
+        }
+
+        private bool LayoutMatches(TextureInfo info)
+        {
+            if (_info.IsLinear != info.IsLinear)
+            {
+                return false;
+            }
+
+            // For linear textures, gob block sizes are ignored.
+            // For block linear textures, the stride is ignored.
+            if (info.IsLinear)
+            {
+                return _info.Stride == info.Stride;
+            }
+            else
+            {
+                return _info.GobBlocksInY == info.GobBlocksInY &&
+                       _info.GobBlocksInZ == info.GobBlocksInZ;
+            }
+        }
+
+        public bool SizeMatches(TextureInfo info)
+        {
+            return SizeMatches(info, alignSizes: false);
+        }
+
+        public bool SizeMatches(TextureInfo info, int level)
+        {
+            return Math.Max(1, _info.Width      >> level) == info.Width  &&
+                   Math.Max(1, _info.Height     >> level) == info.Height &&
+                   Math.Max(1, _info.GetDepth() >> level) == info.GetDepth();
+        }
+
+        private bool SizeMatches(TextureInfo info, bool alignSizes)
+        {
+            if (_info.GetLayers() != info.GetLayers())
+            {
+                return false;
+            }
+
+            if (alignSizes)
+            {
+                Size size0 = GetAlignedSize(_info);
+                Size size1 = GetAlignedSize(info);
+
+                return size0.Width  == size1.Width  &&
+                       size0.Height == size1.Height &&
+                       size0.Depth  == size1.Depth;
+            }
+            else
+            {
+                return _info.Width      == info.Width  &&
+                       _info.Height     == info.Height &&
+                       _info.GetDepth() == info.GetDepth();
+            }
+        }
+
+        private bool SamplerParamsMatches(TextureInfo info)
+        {
+            return _info.DepthStencilMode == info.DepthStencilMode &&
+                   _info.SwizzleR         == info.SwizzleR         &&
+                   _info.SwizzleG         == info.SwizzleG         &&
+                   _info.SwizzleB         == info.SwizzleB         &&
+                   _info.SwizzleA         == info.SwizzleA;
+        }
+
+        private bool TargetAndSamplesCompatible(TextureInfo info)
+        {
+            return _info.Target     == info.Target     &&
+                   _info.SamplesInX == info.SamplesInX &&
+                   _info.SamplesInY == info.SamplesInY;
+        }
+
+        public bool IsViewCompatible(TextureInfo info, ulong size, out int firstLayer, out int firstLevel)
+        {
+            // Out of range.
+            if (info.Address < Address || info.Address + size > EndAddress)
+            {
+                firstLayer = 0;
+                firstLevel = 0;
+
+                return false;
+            }
+
+            int offset = (int)(info.Address - Address);
+
+            if (!_sizeInfo.FindView(offset, (int)size, out firstLayer, out firstLevel))
+            {
+                return false;
+            }
+
+            if (!ViewLayoutCompatible(info, firstLevel))
+            {
+                return false;
+            }
+
+            if (!ViewFormatCompatible(info))
+            {
+                return false;
+            }
+
+            if (!ViewSizeMatches(info, firstLevel))
+            {
+                return false;
+            }
+
+            if (!ViewTargetCompatible(info))
+            {
+                return false;
+            }
+
+            return _info.SamplesInX == info.SamplesInX &&
+                   _info.SamplesInY == info.SamplesInY;
+        }
+
+        private bool ViewLayoutCompatible(TextureInfo info, int level)
+        {
+            if (_info.IsLinear != info.IsLinear)
+            {
+                return false;
+            }
+
+            // For linear textures, gob block sizes are ignored.
+            // For block linear textures, the stride is ignored.
+            if (info.IsLinear)
+            {
+                int width = Math.Max(1, _info.Width >> level);
+
+                int stride = width * _info.FormatInfo.BytesPerPixel;
+
+                stride = BitUtils.AlignUp(stride, 32);
+
+                return stride == info.Stride;
+            }
+            else
+            {
+                int height = Math.Max(1, _info.Height     >> level);
+                int depth  = Math.Max(1, _info.GetDepth() >> level);
+
+                (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+                    height,
+                    depth,
+                    _info.FormatInfo.BlockHeight,
+                    _info.GobBlocksInY,
+                    _info.GobBlocksInZ);
+
+                return gobBlocksInY == info.GobBlocksInY &&
+                       gobBlocksInZ == info.GobBlocksInZ;
+            }
+        }
+
+        private bool ViewFormatCompatible(TextureInfo info)
+        {
+            return TextureCompatibility.FormatCompatible(_info.FormatInfo, info.FormatInfo);
+        }
+
+        private bool ViewSizeMatches(TextureInfo info, int level)
+        {
+            Size size = GetAlignedSize(_info, level);
+
+            Size otherSize = GetAlignedSize(info);
+
+            return size.Width  == otherSize.Width  &&
+                   size.Height == otherSize.Height &&
+                   size.Depth  == otherSize.Depth;
+        }
+
+        private bool ViewTargetCompatible(TextureInfo info)
+        {
+            switch (_info.Target)
+            {
+                case Target.Texture1D:
+                case Target.Texture1DArray:
+                    return info.Target == Target.Texture1D ||
+                           info.Target == Target.Texture1DArray;
+
+                case Target.Texture2D:
+                    return info.Target == Target.Texture2D ||
+                           info.Target == Target.Texture2DArray;
+
+                case Target.Texture2DArray:
+                case Target.Cubemap:
+                case Target.CubemapArray:
+                    return info.Target == Target.Texture2D      ||
+                           info.Target == Target.Texture2DArray ||
+                           info.Target == Target.Cubemap        ||
+                           info.Target == Target.CubemapArray;
+
+                case Target.Texture2DMultisample:
+                case Target.Texture2DMultisampleArray:
+                    return info.Target == Target.Texture2DMultisample ||
+                           info.Target == Target.Texture2DMultisampleArray;
+
+                case Target.Texture3D:
+                    return info.Target == Target.Texture3D;
+            }
+
+            return false;
+        }
+
+        private static Size GetAlignedSize(TextureInfo info, int level = 0)
+        {
+            int width  = Math.Max(1, info.Width  >> level);
+            int height = Math.Max(1, info.Height >> level);
+
+            if (info.IsLinear)
+            {
+                return SizeCalculator.GetLinearAlignedSize(
+                    width,
+                    height,
+                    info.FormatInfo.BlockWidth,
+                    info.FormatInfo.BlockHeight,
+                    info.FormatInfo.BytesPerPixel);
+            }
+            else
+            {
+                int depth = Math.Max(1, info.GetDepth() >> level);
+
+                (int gobBlocksInY, int gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(
+                    height,
+                    depth,
+                    info.FormatInfo.BlockHeight,
+                    info.GobBlocksInY,
+                    info.GobBlocksInZ);
+
+                return SizeCalculator.GetBlockLinearAlignedSize(
+                    width,
+                    height,
+                    depth,
+                    info.FormatInfo.BlockWidth,
+                    info.FormatInfo.BlockHeight,
+                    info.FormatInfo.BytesPerPixel,
+                    gobBlocksInY,
+                    gobBlocksInZ,
+                    info.GobBlocksInTileX);
+            }
+        }
+
+        public ITexture GetTargetTexture(Target target)
+        {
+            if (target == _info.Target)
+            {
+                return HostTexture;
+            }
+
+            if (_arrayViewTexture == null && IsSameDimensionsTarget(target))
+            {
+                TextureCreateInfo createInfo = new TextureCreateInfo(
+                    _info.Width,
+                    _info.Height,
+                    target == Target.CubemapArray ? 6 : 1,
+                    _info.Levels,
+                    _info.Samples,
+                    _info.FormatInfo.BlockWidth,
+                    _info.FormatInfo.BlockHeight,
+                    _info.FormatInfo.BytesPerPixel,
+                    _info.FormatInfo.Format,
+                    _info.DepthStencilMode,
+                    target,
+                    _info.SwizzleR,
+                    _info.SwizzleG,
+                    _info.SwizzleB,
+                    _info.SwizzleA);
+
+                ITexture viewTexture = HostTexture.CreateView(createInfo, 0, 0);
+
+                _arrayViewTexture = viewTexture;
+                _arrayViewTarget  = target;
+
+                return viewTexture;
+            }
+            else if (_arrayViewTarget == target)
+            {
+                return _arrayViewTexture;
+            }
+
+            return null;
+        }
+
+        private bool IsSameDimensionsTarget(Target target)
+        {
+            switch (_info.Target)
+            {
+                case Target.Texture1D:
+                case Target.Texture1DArray:
+                    return target == Target.Texture1D ||
+                           target == Target.Texture1DArray;
+
+                case Target.Texture2D:
+                case Target.Texture2DArray:
+                    return target == Target.Texture2D ||
+                           target == Target.Texture2DArray;
+
+                case Target.Cubemap:
+                case Target.CubemapArray:
+                    return target == Target.Cubemap ||
+                           target == Target.CubemapArray;
+
+                case Target.Texture2DMultisample:
+                case Target.Texture2DMultisampleArray:
+                    return target == Target.Texture2DMultisample ||
+                           target == Target.Texture2DMultisampleArray;
+
+                case Target.Texture3D:
+                    return target == Target.Texture3D;
+            }
+
+            return false;
+        }
+
+        public void ReplaceView(Texture parent, TextureInfo info, ITexture hostTexture)
+        {
+            ReplaceStorage(hostTexture);
+
+            parent._viewStorage.AddView(this);
+
+            SetInfo(info);
+        }
+
+        private void SetInfo(TextureInfo info)
+        {
+            _info = info;
+
+            _depth  = info.GetDepth();
+            _layers = info.GetLayers();
+        }
+
+        private void ReplaceStorage(ITexture hostTexture)
+        {
+            DisposeTextures();
+
+            HostTexture = hostTexture;
+        }
+
+        public bool OverlapsWith(ulong address, ulong size)
+        {
+            return Address < address + size && address < EndAddress;
+        }
+
+        public void Invalidate()
+        {
+            // _hasData = false;
+        }
+
+        public void IncrementReferenceCount()
+        {
+            _referenceCount++;
+        }
+
+        public void DecrementReferenceCount()
+        {
+            if (--_referenceCount == 0)
+            {
+                if (_viewStorage != this)
+                {
+                    _viewStorage.RemoveView(this);
+                }
+
+                _context.Methods.TextureManager.RemoveTextureFromCache(this);
+
+                DisposeTextures();
+            }
+        }
+
+        private void DisposeTextures()
+        {
+            HostTexture.Dispose();
+
+            _arrayViewTexture?.Dispose();
+            _arrayViewTexture = null;
+        }
+    }
+}

+ 17 - 0
Ryujinx.Graphics.Gpu/Image/TextureBindingInfo.cs

@@ -0,0 +1,17 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    struct TextureBindingInfo
+    {
+        public Target Target { get; }
+
+        public int Handle { get; }
+
+        public TextureBindingInfo(Target target, int handle)
+        {
+            Target = target;
+            Handle = handle;
+        }
+    }
+}

+ 95 - 0
Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs

@@ -0,0 +1,95 @@
+using Ryujinx.Graphics.GAL;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    static class TextureCompatibility
+    {
+        private enum FormatClass
+        {
+            Unclassified,
+            BCn64,
+            BCn128,
+            Bc1Rgb,
+            Bc1Rgba,
+            Bc2,
+            Bc3,
+            Bc4,
+            Bc5,
+            Bc6,
+            Bc7
+        }
+
+        public static bool FormatCompatible(FormatInfo lhs, FormatInfo rhs)
+        {
+            if (IsDsFormat(lhs.Format) || IsDsFormat(rhs.Format))
+            {
+                return lhs.Format == rhs.Format;
+            }
+
+            if (lhs.Format.IsAstc() || rhs.Format.IsAstc())
+            {
+                return lhs.Format == rhs.Format;
+            }
+
+            if (lhs.IsCompressed && rhs.IsCompressed)
+            {
+                FormatClass lhsClass = GetFormatClass(lhs.Format);
+                FormatClass rhsClass = GetFormatClass(rhs.Format);
+
+                return lhsClass == rhsClass;
+            }
+            else
+            {
+                return lhs.BytesPerPixel == rhs.BytesPerPixel;
+            }
+        }
+
+        private static FormatClass GetFormatClass(Format format)
+        {
+            switch (format)
+            {
+                case Format.Bc1RgbSrgb:
+                case Format.Bc1RgbUnorm:
+                    return FormatClass.Bc1Rgb;
+                case Format.Bc1RgbaSrgb:
+                case Format.Bc1RgbaUnorm:
+                    return FormatClass.Bc1Rgba;
+                case Format.Bc2Srgb:
+                case Format.Bc2Unorm:
+                    return FormatClass.Bc2;
+                case Format.Bc3Srgb:
+                case Format.Bc3Unorm:
+                    return FormatClass.Bc3;
+                case Format.Bc4Snorm:
+                case Format.Bc4Unorm:
+                    return FormatClass.Bc4;
+                case Format.Bc5Snorm:
+                case Format.Bc5Unorm:
+                    return FormatClass.Bc5;
+                case Format.Bc6HSfloat:
+                case Format.Bc6HUfloat:
+                    return FormatClass.Bc6;
+                case Format.Bc7Srgb:
+                case Format.Bc7Unorm:
+                    return FormatClass.Bc7;
+            }
+
+            return FormatClass.Unclassified;
+        }
+
+        private static bool IsDsFormat(Format format)
+        {
+            switch (format)
+            {
+                case Format.D16Unorm:
+                case Format.D24X8Unorm:
+                case Format.D24UnormS8Uint:
+                case Format.D32Float:
+                case Format.D32FloatS8Uint:
+                    return true;
+            }
+
+            return false;
+        }
+    }
+}

+ 35 - 0
Ryujinx.Graphics.Gpu/Image/TextureComponent.cs

@@ -0,0 +1,35 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    enum TextureComponent
+    {
+        Zero  = 0,
+        Red   = 2,
+        Green = 3,
+        Blue  = 4,
+        Alpha = 5,
+        OneSI = 6,
+        OneF  = 7
+    }
+
+    static class TextureComponentConverter
+    {
+        public static SwizzleComponent Convert(this TextureComponent component)
+        {
+            switch (component)
+            {
+                case TextureComponent.Zero:  return SwizzleComponent.Zero;
+                case TextureComponent.Red:   return SwizzleComponent.Red;
+                case TextureComponent.Green: return SwizzleComponent.Green;
+                case TextureComponent.Blue:  return SwizzleComponent.Blue;
+                case TextureComponent.Alpha: return SwizzleComponent.Alpha;
+                case TextureComponent.OneSI:
+                case TextureComponent.OneF:
+                    return SwizzleComponent.One;
+            }
+
+            return SwizzleComponent.Zero;
+        }
+    }
+}

+ 119 - 0
Ryujinx.Graphics.Gpu/Image/TextureDescriptor.cs

@@ -0,0 +1,119 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    struct TextureDescriptor
+    {
+        public uint Word0;
+        public uint Word1;
+        public uint Word2;
+        public uint Word3;
+        public uint Word4;
+        public uint Word5;
+        public uint Word6;
+        public uint Word7;
+
+        public uint UnpackFormat()
+        {
+            return Word0 & 0x8007ffff;
+        }
+
+        public TextureComponent UnpackSwizzleR()
+        {
+            return(TextureComponent)((Word0 >> 19) & 7);
+        }
+
+        public TextureComponent UnpackSwizzleG()
+        {
+            return(TextureComponent)((Word0 >> 22) & 7);
+        }
+
+        public TextureComponent UnpackSwizzleB()
+        {
+            return(TextureComponent)((Word0 >> 25) & 7);
+        }
+
+        public TextureComponent UnpackSwizzleA()
+        {
+            return(TextureComponent)((Word0 >> 28) & 7);
+        }
+
+        public ulong UnpackAddress()
+        {
+            return Word1 | ((ulong)(Word2 & 0xffff) << 32);
+        }
+
+        public TextureDescriptorType UnpackTextureDescriptorType()
+        {
+            return (TextureDescriptorType)((Word2 >> 21) & 7);
+        }
+
+        public int UnpackStride()
+        {
+            return (int)(Word3 & 0xffff) << 5;
+        }
+
+        public int UnpackGobBlocksInX()
+        {
+            return 1 << (int)(Word3 & 7);
+        }
+
+        public int UnpackGobBlocksInY()
+        {
+            return 1 << (int)((Word3 >> 3) & 7);
+        }
+
+        public int UnpackGobBlocksInZ()
+        {
+            return 1 << (int)((Word3 >> 6) & 7);
+        }
+
+        public int UnpackGobBlocksInTileX()
+        {
+            return 1 << (int)((Word3 >> 10) & 7);
+        }
+
+        public int UnpackLevels()
+        {
+            return (int)(Word3 >> 28) + 1;
+        }
+
+        public int UnpackWidth()
+        {
+            return (int)(Word4 & 0xffff) + 1;
+        }
+
+        public bool UnpackSrgb()
+        {
+            return (Word4 & (1 << 22)) != 0;
+        }
+
+        public TextureTarget UnpackTextureTarget()
+        {
+            return (TextureTarget)((Word4 >> 23) & 0xf);
+        }
+
+        public int UnpackHeight()
+        {
+            return (int)(Word5 & 0xffff) + 1;
+        }
+
+        public int UnpackDepth()
+        {
+            return (int)((Word5 >> 16) & 0x3fff) + 1;
+        }
+
+        public int UnpackBaseLevel()
+        {
+            return (int)(Word7 & 0xf);
+        }
+
+        public int UnpackMaxLevelInclusive()
+        {
+            return (int)((Word7 >> 4) & 0xf);
+        }
+
+        public TextureMsaaMode UnpackTextureMsaaMode()
+        {
+            return (TextureMsaaMode)((Word7 >> 8) & 0xf);
+        }
+    }
+}

+ 11 - 0
Ryujinx.Graphics.Gpu/Image/TextureDescriptorType.cs

@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    enum TextureDescriptorType
+    {
+        Buffer,
+        LinearColorKey,
+        Linear,
+        BlockLinear,
+        BlockLinearColorKey
+    }
+}

+ 101 - 0
Ryujinx.Graphics.Gpu/Image/TextureInfo.cs

@@ -0,0 +1,101 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    struct TextureInfo
+    {
+        public ulong Address { get; }
+
+        public int  Width            { get; }
+        public int  Height           { get; }
+        public int  DepthOrLayers    { get; }
+        public int  Levels           { get; }
+        public int  SamplesInX       { get; }
+        public int  SamplesInY       { get; }
+        public int  Stride           { get; }
+        public bool IsLinear         { get; }
+        public int  GobBlocksInY     { get; }
+        public int  GobBlocksInZ     { get; }
+        public int  GobBlocksInTileX { get; }
+
+        public int Samples => SamplesInX * SamplesInY;
+
+        public Target Target { get; }
+
+        public FormatInfo FormatInfo { get; }
+
+        public DepthStencilMode DepthStencilMode { get; }
+
+        public SwizzleComponent SwizzleR { get; }
+        public SwizzleComponent SwizzleG { get; }
+        public SwizzleComponent SwizzleB { get; }
+        public SwizzleComponent SwizzleA { get; }
+
+        public TextureInfo(
+            ulong            address,
+            int              width,
+            int              height,
+            int              depthOrLayers,
+            int              levels,
+            int              samplesInX,
+            int              samplesInY,
+            int              stride,
+            bool             isLinear,
+            int              gobBlocksInY,
+            int              gobBlocksInZ,
+            int              gobBlocksInTileX,
+            Target           target,
+            FormatInfo       formatInfo,
+            DepthStencilMode depthStencilMode = DepthStencilMode.Depth,
+            SwizzleComponent swizzleR         = SwizzleComponent.Red,
+            SwizzleComponent swizzleG         = SwizzleComponent.Green,
+            SwizzleComponent swizzleB         = SwizzleComponent.Blue,
+            SwizzleComponent swizzleA         = SwizzleComponent.Alpha)
+        {
+            Address          = address;
+            Width            = width;
+            Height           = height;
+            DepthOrLayers    = depthOrLayers;
+            Levels           = levels;
+            SamplesInX       = samplesInX;
+            SamplesInY       = samplesInY;
+            Stride           = stride;
+            IsLinear         = isLinear;
+            GobBlocksInY     = gobBlocksInY;
+            GobBlocksInZ     = gobBlocksInZ;
+            GobBlocksInTileX = gobBlocksInTileX;
+            Target           = target;
+            FormatInfo       = formatInfo;
+            DepthStencilMode = depthStencilMode;
+            SwizzleR         = swizzleR;
+            SwizzleG         = swizzleG;
+            SwizzleB         = swizzleB;
+            SwizzleA         = swizzleA;
+        }
+
+        public int GetDepth()
+        {
+            return Target == Target.Texture3D ? DepthOrLayers : 1;
+        }
+
+        public int GetLayers()
+        {
+            if (Target == Target.Texture2DArray || Target == Target.Texture2DMultisampleArray)
+            {
+                return DepthOrLayers;
+            }
+            else if (Target == Target.CubemapArray)
+            {
+                return DepthOrLayers * 6;
+            }
+            else if (Target == Target.Cubemap)
+            {
+                return 6;
+            }
+            else
+            {
+                return 1;
+            }
+        }
+    }
+}

+ 669 - 0
Ryujinx.Graphics.Gpu/Image/TextureManager.cs

@@ -0,0 +1,669 @@
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Image;
+using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Graphics.Gpu.State;
+using Ryujinx.Graphics.Shader;
+using Ryujinx.Graphics.Texture;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class TextureManager
+    {
+        private GpuContext    _context;
+        private BufferManager _bufferManager;
+
+        private SamplerPool _samplerPool;
+
+        private ulong _texturePoolAddress;
+        private int   _texturePoolMaximumId;
+
+        private TexturePoolCache _texturePoolCache;
+
+        private Texture[] _rtColors;
+        private Texture   _rtColor3D;
+
+        private Texture _rtDepthStencil;
+
+        private ITexture[] _rtHostColors;
+
+        private ITexture _rtHostDs;
+
+        private RangeList<Texture> _textures;
+
+        private AutoDeleteCache _cache;
+
+        private TextureBindingInfo[][] _bindings;
+
+        private struct TextureStatePerStage
+        {
+            public ITexture Texture;
+            public ISampler Sampler;
+        }
+
+        private TextureStatePerStage[][] _textureState;
+
+        private int _textureBufferIndex;
+
+        public TextureManager(GpuContext context, BufferManager bufferManager)
+        {
+            _context       = context;
+            _bufferManager = bufferManager;
+
+            _texturePoolCache = new TexturePoolCache(context, this);
+
+            _rtColors = new Texture[Constants.TotalRenderTargets];
+
+            _rtHostColors = new ITexture[Constants.TotalRenderTargets];
+
+            _textures = new RangeList<Texture>();
+
+            _cache = new AutoDeleteCache();
+
+            _bindings = new TextureBindingInfo[Constants.TotalShaderStages][];
+
+            _textureState = new TextureStatePerStage[Constants.TotalShaderStages][];
+        }
+
+        public void BindTextures(int stage, TextureBindingInfo[] bindings)
+        {
+            _bindings[stage] = bindings;
+
+            _textureState[stage] = new TextureStatePerStage[bindings.Length];
+        }
+
+        public void SetTextureBufferIndex(int index)
+        {
+            _textureBufferIndex = index;
+        }
+
+        public void SetSamplerPool(ulong gpuVa, int maximumId)
+        {
+            ulong address = _context.MemoryManager.Translate(gpuVa);
+
+            if (_samplerPool != null)
+            {
+                if (_samplerPool.Address == address)
+                {
+                    return;
+                }
+
+                _samplerPool.Dispose();
+            }
+
+            _samplerPool = new SamplerPool(_context, address, maximumId);
+        }
+
+        public void SetTexturePool(ulong gpuVa, int maximumId)
+        {
+            ulong address = _context.MemoryManager.Translate(gpuVa);
+
+            _texturePoolAddress   = address;
+            _texturePoolMaximumId = maximumId;
+        }
+
+        public void SetRenderTargetColor(int index, Texture color)
+        {
+            _rtColors[index] = color;
+
+            _rtColor3D = null;
+        }
+
+        public void SetRenderTargetColor3D(Texture color)
+        {
+            _rtColor3D = color;
+        }
+
+        public void SetRenderTargetDepthStencil(Texture depthStencil)
+        {
+            _rtDepthStencil = depthStencil;
+        }
+
+        public void CommitBindings()
+        {
+            UpdateTextures();
+            UpdateRenderTargets();
+        }
+
+        private void UpdateTextures()
+        {
+            TexturePool texturePool = _texturePoolCache.FindOrCreate(
+                _texturePoolAddress,
+                _texturePoolMaximumId);
+
+            for (ShaderStage stage = ShaderStage.Vertex; stage <= ShaderStage.Fragment; stage++)
+            {
+                int stageIndex = (int)stage - 1;
+
+                if (_bindings[stageIndex] == null)
+                {
+                    continue;
+                }
+
+                for (int index = 0; index < _bindings[stageIndex].Length; index++)
+                {
+                    TextureBindingInfo binding = _bindings[stageIndex][index];
+
+                    int packedId = ReadPackedId(stageIndex, binding.Handle);
+
+                    int textureId = (packedId >> 0)  & 0xfffff;
+                    int samplerId = (packedId >> 20) & 0xfff;
+
+                    Texture texture = texturePool.Get(textureId);
+
+                    ITexture hostTexture = texture?.GetTargetTexture(binding.Target);
+
+                    if (_textureState[stageIndex][index].Texture != hostTexture)
+                    {
+                        _textureState[stageIndex][index].Texture = hostTexture;
+
+                        _context.Renderer.GraphicsPipeline.BindTexture(index, stage, hostTexture);
+                    }
+
+                    Sampler sampler = _samplerPool.Get(samplerId);
+
+                    ISampler hostSampler = sampler?.HostSampler;
+
+                    if (_textureState[stageIndex][index].Sampler != hostSampler)
+                    {
+                        _textureState[stageIndex][index].Sampler = hostSampler;
+
+                        _context.Renderer.GraphicsPipeline.BindSampler(index, stage, hostSampler);
+                    }
+                }
+            }
+        }
+
+        private void UpdateRenderTargets()
+        {
+            bool anyChanged = false;
+
+            if (_rtHostDs != _rtDepthStencil?.HostTexture)
+            {
+                _rtHostDs = _rtDepthStencil?.HostTexture;
+
+                anyChanged = true;
+            }
+
+            if (_rtColor3D == null)
+            {
+                for (int index = 0; index < _rtColors.Length; index++)
+                {
+                    ITexture hostTexture = _rtColors[index]?.HostTexture;
+
+                    if (_rtHostColors[index] != hostTexture)
+                    {
+                        _rtHostColors[index] = hostTexture;
+
+                        anyChanged = true;
+                    }
+                }
+
+                if (anyChanged)
+                {
+                    _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+                }
+            }
+            else
+            {
+                if (_rtHostColors[0] != _rtColor3D.HostTexture)
+                {
+                    _rtHostColors[0] = _rtColor3D.HostTexture;
+
+                    anyChanged = true;
+                }
+
+                if (anyChanged)
+                {
+                    _context.Renderer.GraphicsPipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
+                }
+            }
+        }
+
+        private int ReadPackedId(int stage, int wordOffset)
+        {
+            ulong address = _bufferManager.GetGraphicsUniformBufferAddress(stage, _textureBufferIndex);
+
+            address += (uint)wordOffset * 4;
+
+            return BitConverter.ToInt32(_context.PhysicalMemory.Read(address, 4));
+        }
+
+        public Texture FindOrCreateTexture(CopyTexture copyTexture)
+        {
+            ulong address = _context.MemoryManager.Translate(copyTexture.Address.Pack());
+
+            if (address == MemoryManager.BadAddress)
+            {
+                return null;
+            }
+
+            int gobBlocksInY = copyTexture.MemoryLayout.UnpackGobBlocksInY();
+            int gobBlocksInZ = copyTexture.MemoryLayout.UnpackGobBlocksInZ();
+
+            FormatInfo formatInfo = copyTexture.Format.Convert();
+
+            TextureInfo info = new TextureInfo(
+                address,
+                copyTexture.Width,
+                copyTexture.Height,
+                copyTexture.Depth,
+                1,
+                1,
+                1,
+                copyTexture.Stride,
+                copyTexture.LinearLayout,
+                gobBlocksInY,
+                gobBlocksInZ,
+                1,
+                Target.Texture2D,
+                formatInfo);
+
+            Texture texture = FindOrCreateTexture(info, TextureSearchFlags.IgnoreMs);
+
+            texture.SynchronizeMemory();
+
+            return texture;
+        }
+
+        public Texture FindOrCreateTexture(RtColorState colorState, int samplesInX, int samplesInY)
+        {
+            ulong address = _context.MemoryManager.Translate(colorState.Address.Pack());
+
+            if (address == MemoryManager.BadAddress)
+            {
+                return null;
+            }
+
+            bool isLinear = colorState.MemoryLayout.UnpackIsLinear();
+
+            int gobBlocksInY = colorState.MemoryLayout.UnpackGobBlocksInY();
+            int gobBlocksInZ = colorState.MemoryLayout.UnpackGobBlocksInZ();
+
+            Target target;
+
+            if (colorState.MemoryLayout.UnpackIsTarget3D())
+            {
+                target = Target.Texture3D;
+            }
+            else if ((samplesInX | samplesInY) != 1)
+            {
+                target = colorState.Depth > 1
+                    ? Target.Texture2DMultisampleArray
+                    : Target.Texture2DMultisample;
+            }
+            else
+            {
+                target = colorState.Depth > 1
+                    ? Target.Texture2DArray
+                    : Target.Texture2D;
+            }
+
+            FormatInfo formatInfo = colorState.Format.Convert();
+
+            int width, stride;
+
+            // For linear textures, the width value is actually the stride.
+            // We can easily get the width by dividing the stride by the bpp,
+            // since the stride is the total number of bytes occupied by a
+            // line. The stride should also meet alignment constraints however,
+            // so the width we get here is the aligned width.
+            if (isLinear)
+            {
+                width  = colorState.WidthOrStride / formatInfo.BytesPerPixel;
+                stride = colorState.WidthOrStride;
+            }
+            else
+            {
+                width  = colorState.WidthOrStride;
+                stride = 0;
+            }
+
+            TextureInfo info = new TextureInfo(
+                address,
+                width,
+                colorState.Height,
+                colorState.Depth,
+                1,
+                samplesInX,
+                samplesInY,
+                stride,
+                isLinear,
+                gobBlocksInY,
+                gobBlocksInZ,
+                1,
+                target,
+                formatInfo);
+
+            Texture texture = FindOrCreateTexture(info);
+
+            texture.SynchronizeMemory();
+
+            return texture;
+        }
+
+        public Texture FindOrCreateTexture(RtDepthStencilState dsState, Size3D size, int samplesInX, int samplesInY)
+        {
+            ulong address = _context.MemoryManager.Translate(dsState.Address.Pack());
+
+            if (address == MemoryManager.BadAddress)
+            {
+                return null;
+            }
+
+            int gobBlocksInY = dsState.MemoryLayout.UnpackGobBlocksInY();
+            int gobBlocksInZ = dsState.MemoryLayout.UnpackGobBlocksInZ();
+
+            Target target = (samplesInX | samplesInY) != 1
+                ? Target.Texture2DMultisample
+                : Target.Texture2D;
+
+            FormatInfo formatInfo = dsState.Format.Convert();
+
+            TextureInfo info = new TextureInfo(
+                address,
+                size.Width,
+                size.Height,
+                size.Depth,
+                1,
+                samplesInX,
+                samplesInY,
+                0,
+                false,
+                gobBlocksInY,
+                gobBlocksInZ,
+                1,
+                target,
+                formatInfo);
+
+            Texture texture = FindOrCreateTexture(info);
+
+            texture.SynchronizeMemory();
+
+            return texture;
+        }
+
+        public Texture FindOrCreateTexture(TextureInfo info, TextureSearchFlags flags = TextureSearchFlags.None)
+        {
+            bool isSamplerTexture = (flags & TextureSearchFlags.Sampler) != 0;
+
+            // Try to find a perfect texture match, with the same address and parameters.
+            Texture[] sameAddressOverlaps = _textures.FindOverlaps(info.Address);
+
+            foreach (Texture overlap in sameAddressOverlaps)
+            {
+                if (overlap.IsPerfectMatch(info, flags))
+                {
+                    if (!isSamplerTexture)
+                    {
+                        // If not a sampler texture, it is managed by the auto delete
+                        // cache, ensure that it is on the "top" of the list to avoid
+                        // deletion.
+                        _cache.Lift(overlap);
+                    }
+                    else if (!overlap.SizeMatches(info))
+                    {
+                        // If this is used for sampling, the size must match,
+                        // otherwise the shader would sample garbage data.
+                        // To fix that, we create a new texture with the correct
+                        // size, and copy the data from the old one to the new one.
+                        overlap.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+                    }
+
+                    return overlap;
+                }
+            }
+
+            // Calculate texture sizes, used to find all overlapping textures.
+            SizeInfo sizeInfo;
+
+            if (info.IsLinear)
+            {
+                sizeInfo = SizeCalculator.GetLinearTextureSize(
+                    info.Stride,
+                    info.Height,
+                    info.FormatInfo.BlockHeight);
+            }
+            else
+            {
+                sizeInfo = SizeCalculator.GetBlockLinearTextureSize(
+                    info.Width,
+                    info.Height,
+                    info.GetDepth(),
+                    info.Levels,
+                    info.GetLayers(),
+                    info.FormatInfo.BlockWidth,
+                    info.FormatInfo.BlockHeight,
+                    info.FormatInfo.BytesPerPixel,
+                    info.GobBlocksInY,
+                    info.GobBlocksInZ,
+                    info.GobBlocksInTileX);
+            }
+
+            // Find view compatible matches.
+            ulong size = (ulong)sizeInfo.TotalSize;
+
+            Texture[] overlaps = _textures.FindOverlaps(info.Address, size);
+
+            Texture texture = null;
+
+            foreach (Texture overlap in overlaps)
+            {
+                if (overlap.IsViewCompatible(info, size, out int firstLayer, out int firstLevel))
+                {
+                    if (!isSamplerTexture)
+                    {
+                        info = AdjustSizes(overlap, info, firstLevel);
+                    }
+
+                    texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel);
+
+                    // The size only matters (and is only really reliable) when the
+                    // texture is used on a sampler, because otherwise the size will be
+                    // aligned.
+                    if (!overlap.SizeMatches(info, firstLevel) && isSamplerTexture)
+                    {
+                        texture.ChangeSize(info.Width, info.Height, info.DepthOrLayers);
+                    }
+
+                    break;
+                }
+            }
+
+            // No match, create a new texture.
+            if (texture == null)
+            {
+                texture = new Texture(_context, info, sizeInfo);
+
+                // We need to synchronize before copying the old view data to the texture,
+                // otherwise the copied data would be overwritten by a future synchronization.
+                texture.SynchronizeMemory();
+
+                foreach (Texture overlap in overlaps)
+                {
+                    if (texture.IsViewCompatible(overlap.Info, overlap.Size, out int firstLayer, out int firstLevel))
+                    {
+                        TextureInfo overlapInfo = AdjustSizes(texture, overlap.Info, firstLevel);
+
+                        TextureCreateInfo createInfo = GetCreateInfo(overlapInfo, _context.Capabilities);
+
+                        ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
+
+                        overlap.HostTexture.CopyTo(newView);
+
+                        overlap.ReplaceView(texture, overlapInfo, newView);
+                    }
+                }
+            }
+
+            // Sampler textures are managed by the texture pool, all other textures
+            // are managed by the auto delete cache.
+            if (!isSamplerTexture)
+            {
+                _cache.Add(texture);
+            }
+
+            _textures.Add(texture);
+
+            return texture;
+        }
+
+        private static TextureInfo AdjustSizes(Texture parent, TextureInfo info, int firstLevel)
+        {
+            // When the texture is used as view of another texture, we must
+            // ensure that the sizes are valid, otherwise data uploads would fail
+            // (and the size wouldn't match the real size used on the host API).
+            // Given a parent texture from where the view is created, we have the
+            // following rules:
+            // - The view size must be equal to the parent size, divided by (2 ^ l),
+            // where l is the first mipmap level of the view. The division result must
+            // be rounded down, and the result must be clamped to 1.
+            // - If the parent format is compressed, and the view format isn't, the
+            // view size is calculated as above, but the width and height of the
+            // view must be also divided by the compressed format block width and height.
+            // - If the parent format is not compressed, and the view is, the view
+            // size is calculated as described on the first point, but the width and height
+            // of the view must be also multiplied by the block width and height.
+            int width  = Math.Max(1, parent.Info.Width  >> firstLevel);
+            int height = Math.Max(1, parent.Info.Height >> firstLevel);
+
+            if (parent.Info.FormatInfo.IsCompressed && !info.FormatInfo.IsCompressed)
+            {
+                width  = BitUtils.DivRoundUp(width,  parent.Info.FormatInfo.BlockWidth);
+                height = BitUtils.DivRoundUp(height, parent.Info.FormatInfo.BlockHeight);
+            }
+            else if (!parent.Info.FormatInfo.IsCompressed && info.FormatInfo.IsCompressed)
+            {
+                width  *= info.FormatInfo.BlockWidth;
+                height *= info.FormatInfo.BlockHeight;
+            }
+
+            int depthOrLayers;
+
+            if (info.Target == Target.Texture3D)
+            {
+                depthOrLayers = Math.Max(1, parent.Info.DepthOrLayers >> firstLevel);
+            }
+            else
+            {
+                depthOrLayers = info.DepthOrLayers;
+            }
+
+            return new TextureInfo(
+                info.Address,
+                width,
+                height,
+                depthOrLayers,
+                info.Levels,
+                info.SamplesInX,
+                info.SamplesInY,
+                info.Stride,
+                info.IsLinear,
+                info.GobBlocksInY,
+                info.GobBlocksInZ,
+                info.GobBlocksInTileX,
+                info.Target,
+                info.FormatInfo,
+                info.DepthStencilMode,
+                info.SwizzleR,
+                info.SwizzleG,
+                info.SwizzleB,
+                info.SwizzleA);
+        }
+
+        public static TextureCreateInfo GetCreateInfo(TextureInfo info, Capabilities caps)
+        {
+            FormatInfo formatInfo = info.FormatInfo;
+
+            if (!caps.SupportsAstcCompression)
+            {
+                if (formatInfo.Format.IsAstcUnorm())
+                {
+                    formatInfo = new FormatInfo(Format.R8G8B8A8Unorm, 1, 1, 4);
+                }
+                else if (formatInfo.Format.IsAstcSrgb())
+                {
+                    formatInfo = new FormatInfo(Format.R8G8B8A8Srgb, 1, 1, 4);
+                }
+            }
+
+            int width  = info.Width  / info.SamplesInX;
+            int height = info.Height / info.SamplesInY;
+
+            int depth = info.GetDepth() * info.GetLayers();
+
+            return new TextureCreateInfo(
+                width,
+                height,
+                depth,
+                info.Levels,
+                info.Samples,
+                formatInfo.BlockWidth,
+                formatInfo.BlockHeight,
+                formatInfo.BytesPerPixel,
+                formatInfo.Format,
+                info.DepthStencilMode,
+                info.Target,
+                info.SwizzleR,
+                info.SwizzleG,
+                info.SwizzleB,
+                info.SwizzleA);
+        }
+
+        public Texture Find2(ulong address)
+        {
+            Texture[] ts = _textures.FindOverlaps(address, 1);
+
+            if (ts.Length == 2)
+            {
+                return ts[1];
+            }
+
+            if (ts.Length == 0)
+            {
+                ts = _textures.FindOverlaps(address - 1, 2);
+            }
+
+            if (ts.Length == 0)
+            {
+                return null;
+            }
+
+            return ts[0];
+        }
+
+        public void InvalidateRange(ulong address, ulong size)
+        {
+            Texture[] overlaps = _textures.FindOverlaps(address, size);
+
+            foreach (Texture overlap in overlaps)
+            {
+                overlap.Invalidate();
+            }
+
+            _samplerPool?.InvalidateRange(address, size);
+
+            _texturePoolCache.InvalidateRange(address, size);
+        }
+
+        public void Flush()
+        {
+            foreach (Texture texture in _cache)
+            {
+                if (texture.Info.IsLinear && texture.Modified)
+                {
+                    texture.Flush();
+
+                    texture.Modified = false;
+                }
+            }
+        }
+
+        public void RemoveTextureFromCache(Texture texture)
+        {
+            _textures.Remove(texture);
+        }
+    }
+}

+ 53 - 0
Ryujinx.Graphics.Gpu/Image/TextureMsaaMode.cs

@@ -0,0 +1,53 @@
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    enum TextureMsaaMode
+    {
+        Ms1x1 = 0,
+        Ms2x2 = 2,
+        Ms4x2 = 4,
+        Ms2x1 = 5,
+        Ms4x4 = 6
+    }
+
+    static class TextureMsaaModeConverter
+    {
+        public static int SamplesCount(this TextureMsaaMode msaaMode)
+        {
+            switch (msaaMode)
+            {
+                case TextureMsaaMode.Ms2x1: return 2;
+                case TextureMsaaMode.Ms2x2: return 4;
+                case TextureMsaaMode.Ms4x2: return 8;
+                case TextureMsaaMode.Ms4x4: return 16;
+            }
+
+            return 1;
+        }
+
+        public static int SamplesInX(this TextureMsaaMode msaaMode)
+        {
+            switch (msaaMode)
+            {
+                case TextureMsaaMode.Ms2x1: return 2;
+                case TextureMsaaMode.Ms2x2: return 2;
+                case TextureMsaaMode.Ms4x2: return 4;
+                case TextureMsaaMode.Ms4x4: return 4;
+            }
+
+            return 1;
+        }
+
+        public static int SamplesInY(this TextureMsaaMode msaaMode)
+        {
+            switch (msaaMode)
+            {
+                case TextureMsaaMode.Ms2x1: return 1;
+                case TextureMsaaMode.Ms2x2: return 2;
+                case TextureMsaaMode.Ms4x2: return 2;
+                case TextureMsaaMode.Ms4x4: return 4;
+            }
+
+            return 1;
+        }
+    }
+}

+ 219 - 0
Ryujinx.Graphics.Gpu/Image/TexturePool.cs

@@ -0,0 +1,219 @@
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.GAL.Texture;
+using Ryujinx.Graphics.Gpu.Memory;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class TexturePool : Pool<Texture>
+    {
+        private TextureManager _textureManager;
+
+        public LinkedListNode<TexturePool> CacheNode { get; set; }
+
+        private struct TextureContainer
+        {
+            public Texture Texture0 { get; set; }
+            public Texture Texture1 { get; set; }
+        }
+
+        public TexturePool(
+            GpuContext     context,
+            TextureManager textureManager,
+            ulong          address,
+            int            maximumId) : base(context, address, maximumId)
+        {
+            _textureManager = textureManager;
+        }
+
+        public override Texture Get(int id)
+        {
+            if ((uint)id >= Items.Length)
+            {
+                return null;
+            }
+
+            SynchronizeMemory();
+
+            Texture texture = Items[id];
+
+            if (texture == null)
+            {
+                ulong address = Address + (ulong)(uint)id * DescriptorSize;
+
+                Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+                TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+                TextureInfo info = GetInfo(descriptor);
+
+                // Bad address. We can't add a texture with a invalid address
+                // to the cache.
+                if (info.Address == MemoryManager.BadAddress)
+                {
+                    return null;
+                }
+
+                texture = _textureManager.FindOrCreateTexture(info, TextureSearchFlags.Sampler);
+
+                texture.IncrementReferenceCount();
+
+                Items[id] = texture;
+            }
+            else
+            {
+                // Memory is automatically synchronized on texture creation.
+                texture.SynchronizeMemory();
+            }
+
+            return texture;
+        }
+
+        protected override void InvalidateRangeImpl(ulong address, ulong size)
+        {
+            ulong endAddress = address + size;
+
+            for (; address < endAddress; address += DescriptorSize)
+            {
+                int id = (int)((address - Address) / DescriptorSize);
+
+                Texture texture = Items[id];
+
+                if (texture != null)
+                {
+                    Span<byte> data = Context.PhysicalMemory.Read(address, DescriptorSize);
+
+                    TextureDescriptor descriptor = MemoryMarshal.Cast<byte, TextureDescriptor>(data)[0];
+
+                    // If the descriptors are the same, the texture is the same,
+                    // we don't need to remove as it was not modified. Just continue.
+                    if (texture.IsPerfectMatch(GetInfo(descriptor), TextureSearchFlags.Strict))
+                    {
+                        continue;
+                    }
+
+                    texture.DecrementReferenceCount();
+
+                    Items[id] = null;
+                }
+            }
+        }
+
+        private TextureInfo GetInfo(TextureDescriptor descriptor)
+        {
+            ulong address = Context.MemoryManager.Translate(descriptor.UnpackAddress());
+
+            int width         = descriptor.UnpackWidth();
+            int height        = descriptor.UnpackHeight();
+            int depthOrLayers = descriptor.UnpackDepth();
+            int levels        = descriptor.UnpackLevels();
+
+            TextureMsaaMode msaaMode = descriptor.UnpackTextureMsaaMode();
+
+            int samplesInX = msaaMode.SamplesInX();
+            int samplesInY = msaaMode.SamplesInY();
+
+            int stride = descriptor.UnpackStride();
+
+            TextureDescriptorType descriptorType = descriptor.UnpackTextureDescriptorType();
+
+            bool isLinear = descriptorType == TextureDescriptorType.Linear;
+
+            Target target = descriptor.UnpackTextureTarget().Convert((samplesInX | samplesInY) != 1);
+
+            uint format = descriptor.UnpackFormat();
+            bool srgb   = descriptor.UnpackSrgb();
+
+            if (!FormatTable.TryGetTextureFormat(format, srgb, out FormatInfo formatInfo))
+            {
+                // TODO: Warning.
+
+                formatInfo = FormatInfo.Default;
+            }
+
+            int gobBlocksInY = descriptor.UnpackGobBlocksInY();
+            int gobBlocksInZ = descriptor.UnpackGobBlocksInZ();
+
+            int gobBlocksInTileX = descriptor.UnpackGobBlocksInTileX();
+
+            SwizzleComponent swizzleR = descriptor.UnpackSwizzleR().Convert();
+            SwizzleComponent swizzleG = descriptor.UnpackSwizzleG().Convert();
+            SwizzleComponent swizzleB = descriptor.UnpackSwizzleB().Convert();
+            SwizzleComponent swizzleA = descriptor.UnpackSwizzleA().Convert();
+
+            DepthStencilMode depthStencilMode = GetDepthStencilMode(
+                formatInfo.Format,
+                swizzleR,
+                swizzleG,
+                swizzleB,
+                swizzleA);
+
+            return new TextureInfo(
+                address,
+                width,
+                height,
+                depthOrLayers,
+                levels,
+                samplesInX,
+                samplesInY,
+                stride,
+                isLinear,
+                gobBlocksInY,
+                gobBlocksInZ,
+                gobBlocksInTileX,
+                target,
+                formatInfo,
+                depthStencilMode,
+                swizzleR,
+                swizzleG,
+                swizzleB,
+                swizzleA);
+        }
+
+        private static DepthStencilMode GetDepthStencilMode(Format format, params SwizzleComponent[] components)
+        {
+            // R = Depth, G = Stencil.
+            // On 24-bits depth formats, this is inverted (Stencil is R etc).
+            // NVN setup:
+            // For depth, A is set to 1.0f, the other components are set to Depth.
+            // For stencil, all components are set to Stencil.
+            SwizzleComponent component = components[0];
+
+            for (int index = 1; index < 4 && !IsRG(component); index++)
+            {
+                component = components[index];
+            }
+
+            if (!IsRG(component))
+            {
+                return DepthStencilMode.Depth;
+            }
+
+            if (format == Format.D24X8Unorm || format == Format.D24UnormS8Uint)
+            {
+                return component == SwizzleComponent.Red
+                    ? DepthStencilMode.Stencil
+                    : DepthStencilMode.Depth;
+            }
+            else
+            {
+                return component == SwizzleComponent.Red
+                    ? DepthStencilMode.Depth
+                    : DepthStencilMode.Stencil;
+            }
+        }
+
+        private static bool IsRG(SwizzleComponent component)
+        {
+            return component == SwizzleComponent.Red ||
+                   component == SwizzleComponent.Green;
+        }
+
+        protected override void Delete(Texture item)
+        {
+            item?.DecrementReferenceCount();
+        }
+    }
+}

+ 73 - 0
Ryujinx.Graphics.Gpu/Image/TexturePoolCache.cs

@@ -0,0 +1,73 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    class TexturePoolCache
+    {
+        private const int MaxCapacity = 4;
+
+        private GpuContext     _context;
+        private TextureManager _textureManager;
+
+        private LinkedList<TexturePool> _pools;
+
+        public TexturePoolCache(GpuContext context, TextureManager textureManager)
+        {
+            _context        = context;
+            _textureManager = textureManager;
+
+            _pools = new LinkedList<TexturePool>();
+        }
+
+        public TexturePool FindOrCreate(ulong address, int maximumId)
+        {
+            TexturePool pool;
+
+            // First we try to find the pool.
+            for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
+            {
+                pool = node.Value;
+
+                if (pool.Address == address)
+                {
+                    if (pool.CacheNode != _pools.Last)
+                    {
+                        _pools.Remove(pool.CacheNode);
+
+                        pool.CacheNode = _pools.AddLast(pool);
+                    }
+
+                    return pool;
+                }
+            }
+
+            // If not found, create a new one.
+            pool = new TexturePool(_context, _textureManager, address, maximumId);
+
+            pool.CacheNode = _pools.AddLast(pool);
+
+            if (_pools.Count > MaxCapacity)
+            {
+                TexturePool oldestPool = _pools.First.Value;
+
+                _pools.RemoveFirst();
+
+                oldestPool.Dispose();
+
+                oldestPool.CacheNode = null;
+            }
+
+            return pool;
+        }
+
+        public void InvalidateRange(ulong address, ulong size)
+        {
+            for (LinkedListNode<TexturePool> node = _pools.First; node != null; node = node.Next)
+            {
+                TexturePool pool = node.Value;
+
+                pool.InvalidateRange(address, size);
+            }
+        }
+    }
+}

+ 13 - 0
Ryujinx.Graphics.Gpu/Image/TextureSearchFlags.cs

@@ -0,0 +1,13 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    [Flags]
+    enum TextureSearchFlags
+    {
+        None     = 0,
+        IgnoreMs = 1 << 0,
+        Strict   = 1 << 1 | Sampler,
+        Sampler  = 1 << 2
+    }
+}

+ 49 - 0
Ryujinx.Graphics.Gpu/Image/TextureTarget.cs

@@ -0,0 +1,49 @@
+using Ryujinx.Graphics.GAL.Texture;
+
+namespace Ryujinx.Graphics.Gpu.Image
+{
+    enum TextureTarget
+    {
+        Texture1D,
+        Texture2D,
+        Texture3D,
+        Cubemap,
+        Texture1DArray,
+        Texture2DArray,
+        TextureBuffer,
+        Texture2DLinear,
+        CubemapArray
+    }
+
+    static class TextureTargetConverter
+    {
+        public static Target Convert(this TextureTarget target, bool isMultisample)
+        {
+            if (isMultisample)
+            {
+                switch (target)
+                {
+                    case TextureTarget.Texture2D:      return Target.Texture2DMultisample;
+                    case TextureTarget.Texture2DArray: return Target.Texture2DMultisampleArray;
+                }
+            }
+            else
+            {
+                switch (target)
+                {
+                    case TextureTarget.Texture1D:       return Target.Texture1D;
+                    case TextureTarget.Texture2D:       return Target.Texture2D;
+                    case TextureTarget.Texture2DLinear: return Target.Texture2D;
+                    case TextureTarget.Texture3D:       return Target.Texture3D;
+                    case TextureTarget.Texture1DArray:  return Target.Texture1DArray;
+                    case TextureTarget.Texture2DArray:  return Target.Texture2DArray;
+                    case TextureTarget.Cubemap:         return Target.Cubemap;
+                    case TextureTarget.CubemapArray:    return Target.CubemapArray;
+                    case TextureTarget.TextureBuffer:   return Target.TextureBuffer;
+                }
+            }
+
+            return Target.Texture1D;
+        }
+    }
+}

+ 19 - 20
Ryujinx.Graphics/Graphics3d/MacroInterpreter.cs → Ryujinx.Graphics.Gpu/MacroInterpreter.cs

@@ -1,9 +1,8 @@
 using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Memory;
 using System;
 using System.Collections.Generic;
 
-namespace Ryujinx.Graphics.Graphics3d
+namespace Ryujinx.Graphics.Gpu
 {
     class MacroInterpreter
     {
@@ -42,8 +41,9 @@ namespace Ryujinx.Graphics.Graphics3d
             BitwiseNotAnd      = 12
         }
 
-        private NvGpuFifo    _pFifo;
-        private INvGpuEngine _engine;
+        private GpuContext _context;
+
+        private NvGpuFifo _pFifo;
 
         public Queue<int> Fifo { get; private set; }
 
@@ -60,17 +60,17 @@ namespace Ryujinx.Graphics.Graphics3d
 
         private int _pc;
 
-        public MacroInterpreter(NvGpuFifo pFifo, INvGpuEngine engine)
+        public MacroInterpreter(GpuContext context, NvGpuFifo pFifo)
         {
-            _pFifo  = pFifo;
-            _engine = engine;
+            _context = context;
+            _pFifo   = pFifo;
 
             Fifo = new Queue<int>();
 
             _gprs = new int[8];
         }
 
-        public void Execute(NvGpuVmm vmm, int[] mme, int position, int param)
+        public void Execute(int[] mme, int position, int param)
         {
             Reset();
 
@@ -80,11 +80,11 @@ namespace Ryujinx.Graphics.Graphics3d
 
             FetchOpCode(mme);
 
-            while (Step(vmm, mme));
+            while (Step(mme));
 
             // Due to the delay slot, we still need to execute
             // one more instruction before we actually exit.
-            Step(vmm, mme);
+            Step(mme);
         }
 
         private void Reset()
@@ -100,7 +100,7 @@ namespace Ryujinx.Graphics.Graphics3d
             _carry = false;
         }
 
-        private bool Step(NvGpuVmm vmm, int[] mme)
+        private bool Step(int[] mme)
         {
             int baseAddr = _pc - 1;
 
@@ -146,7 +146,7 @@ namespace Ryujinx.Graphics.Graphics3d
                     {
                         SetDstGpr(FetchParam());
 
-                        Send(vmm, result);
+                        Send(result);
 
                         break;
                     }
@@ -156,7 +156,7 @@ namespace Ryujinx.Graphics.Graphics3d
                     {
                         SetDstGpr(result);
 
-                        Send(vmm, result);
+                        Send(result);
 
                         break;
                     }
@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.Graphics3d
 
                         SetMethAddr(result);
 
-                        Send(vmm, FetchParam());
+                        Send(FetchParam());
 
                         break;
                     }
@@ -190,7 +190,7 @@ namespace Ryujinx.Graphics.Graphics3d
 
                         SetMethAddr(result);
 
-                        Send(vmm, (result >> 12) & 0x3f);
+                        Send((result >> 12) & 0x3f);
 
                         break;
                     }
@@ -228,7 +228,6 @@ namespace Ryujinx.Graphics.Graphics3d
         private void FetchOpCode(int[] mme)
         {
             _opCode = _pipeOp;
-
             _pipeOp = mme[_pc++];
         }
 
@@ -401,14 +400,14 @@ namespace Ryujinx.Graphics.Graphics3d
 
         private int Read(int reg)
         {
-            return _engine.Registers[reg];
+            return _context.State.Read(reg);
         }
 
-        private void Send(NvGpuVmm vmm, int value)
+        private void Send(int value)
         {
-            GpuMethodCall methCall = new GpuMethodCall(_methAddr, value);
+            MethodParams meth = new MethodParams(_methAddr, value);
 
-            _engine.CallMethod(vmm, methCall);
+            _context.State.CallMethod(meth);
 
             _methAddr += _methIncr;
         }

+ 99 - 0
Ryujinx.Graphics.Gpu/Memory/Buffer.cs

@@ -0,0 +1,99 @@
+using Ryujinx.Graphics.GAL;
+using System;
+
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+    class Buffer : IRange<Buffer>, IDisposable
+    {
+        private GpuContext _context;
+
+        private IBuffer _buffer;
+
+        public ulong Address { get; }
+        public ulong Size    { get; }
+
+        public ulong EndAddress => Address + Size;
+
+        private int[] _sequenceNumbers;
+
+        public Buffer(GpuContext context, ulong address, ulong size)
+        {
+            _context = context;
+            Address  = address;
+            Size     = size;
+
+            _buffer = context.Renderer.CreateBuffer((int)size);
+
+            _sequenceNumbers = new int[size / MemoryManager.PageSize];
+
+            Invalidate();
+        }
+
+        public BufferRange GetRange(ulong address, ulong size)
+        {
+            int offset = (int)(address - Address);
+
+            return new BufferRange(_buffer, offset, (int)size);
+        }
+
+        public bool OverlapsWith(ulong address, ulong size)
+        {
+            return Address < address + size && address < EndAddress;
+        }
+
+        public void SynchronizeMemory(ulong address, ulong size)
+        {
+            int currentSequenceNumber = _context.SequenceNumber;
+
+            bool needsSync = false;
+
+            ulong buffOffset = address - Address;
+
+            ulong buffEndOffset = (buffOffset + size + MemoryManager.PageMask) & ~MemoryManager.PageMask;
+
+            int startIndex = (int)(buffOffset    / MemoryManager.PageSize);
+            int endIndex   = (int)(buffEndOffset / MemoryManager.PageSize);
+
+            for (int index = startIndex; index < endIndex; index++)
+            {
+                if (_sequenceNumbers[index] != currentSequenceNumber)
+                {
+                    _sequenceNumbers[index] = currentSequenceNumber;
+
+                    needsSync = true;
+                }
+            }
+
+            if (!needsSync)
+            {
+                return;
+            }
+
+            (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(address, size);
+
+            for (int index = 0; index < modifiedRanges.Length; index++)
+            {
+                (ulong mAddress, ulong mSize) = modifiedRanges[index];
+
+                int offset = (int)(mAddress - Address);
+
+                _buffer.SetData(offset, _context.PhysicalMemory.Read(mAddress, mSize));
+            }
+        }
+
+        public void CopyTo(Buffer destination, int dstOffset)
+        {
+            _buffer.CopyTo(destination._buffer, 0, dstOffset, (int)Size);
+        }
+
+        public void Invalidate()
+        {
+            _buffer.SetData(0, _context.PhysicalMemory.Read(Address, Size));
+        }
+
+        public void Dispose()
+        {
+            _buffer.Dispose();
+        }
+    }
+}

+ 8 - 0
Ryujinx.Graphics.Gpu/Memory/BufferBounds.cs

@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+    struct BufferBounds
+    {
+        public ulong Address;
+        public ulong Size;
+    }
+}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio