From 726de8c46ab10f1b0684fe14bca1ca96ba6d2832 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Sun, 19 Aug 2018 22:25:26 -0300
Subject: [PATCH] Rendertarget attachments, texture and image changes (#358)

* Add multiple color outputs for fragment shaders

* Add registers and gal enums

* Use textures for framebuffers and split color and zeta framebuffers

* Abstract texture and framebuffer targets as an image

* Share images between framebuffers and textures

* Unstub formats

* Add some formats

* Disable multiple attachments

* Cache framebuffer attachments

* Handle format types

* Add some rendertarget formats

* Code cleanup

* Fixup half float types

* Address feedback

* Disable multiple attachments in shaders

* Add A4B4G4R4 image format

* Add reversed section for image enums
---
 Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs  |  68 +++
 .../Gal/{GalTexture.cs => GalImage.cs}        |  16 +-
 Ryujinx.Graphics/Gal/GalImageFormat.cs        | 204 +++++++
 Ryujinx.Graphics/Gal/GalTextureFormat.cs      |   1 +
 Ryujinx.Graphics/Gal/GalTextureType.cs        |  13 +
 Ryujinx.Graphics/Gal/GalZetaFormat.cs         |  16 +
 Ryujinx.Graphics/Gal/IGalFrameBuffer.cs       |   9 +-
 Ryujinx.Graphics/Gal/IGalRasterizer.cs        |   1 +
 Ryujinx.Graphics/Gal/IGalTexture.cs           |   6 +-
 Ryujinx.Graphics/Gal/ImageFormatConverter.cs  | 263 +++++++++
 Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs   | 124 ++++
 Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs |   6 +-
 .../Gal/OpenGL/OGLEnumConverter.cs            |  77 ++-
 Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs | 528 ++++++++++--------
 Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs  |  19 +-
 Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs    |   6 +-
 Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs     | 181 +++---
 Ryujinx.Graphics/Gal/Shader/GlslDecl.cs       |   7 +-
 Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs |  11 +-
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs      |   4 -
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs      |  71 ++-
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs   |   8 +
 Ryujinx.HLE/Gpu/Texture/TextureFactory.cs     |  11 +-
 Ryujinx.HLE/Gpu/Texture/TextureHelper.cs      | 150 +++--
 Ryujinx.HLE/Gpu/Texture/TextureReader.cs      |   1 +
 25 files changed, 1360 insertions(+), 441 deletions(-)
 create mode 100644 Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs
 rename Ryujinx.Graphics/Gal/{GalTexture.cs => GalImage.cs} (61%)
 create mode 100644 Ryujinx.Graphics/Gal/GalImageFormat.cs
 create mode 100644 Ryujinx.Graphics/Gal/GalTextureType.cs
 create mode 100644 Ryujinx.Graphics/Gal/GalZetaFormat.cs
 create mode 100644 Ryujinx.Graphics/Gal/ImageFormatConverter.cs
 create mode 100644 Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs

diff --git a/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs b/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs
new file mode 100644
index 00000000..3180aeff
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalFrameBufferFormat.cs
@@ -0,0 +1,68 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalFrameBufferFormat
+    {
+        Bitmap               = 0x1c,
+        Unknown1D            = 0x1d,
+        RGBA32Float          = 0xc0,
+        RGBA32Sint           = 0xc1,
+        RGBA32Uint           = 0xc2,
+        RGBX32Float          = 0xc3,
+        RGBX32Sint           = 0xc4,
+        RGBX32Uint           = 0xc5,
+        RGBA16Unorm          = 0xc6,
+        RGBA16Snorm          = 0xc7,
+        RGBA16Sint           = 0xc8,
+        RGBA16Uint           = 0xc9,
+        RGBA16Float          = 0xca,
+        RG32Float            = 0xcb,
+        RG32Sint             = 0xcc,
+        RG32Uint             = 0xcd,
+        RGBX16Float          = 0xce,
+        BGRA8Unorm           = 0xcf,
+        BGRA8Srgb            = 0xd0,
+        RGB10A2Unorm         = 0xd1,
+        RGB10A2Uint          = 0xd2,
+        RGBA8Unorm           = 0xd5,
+        RGBA8Srgb            = 0xd6,
+        RGBA8Snorm           = 0xd7,
+        RGBA8Sint            = 0xd8,
+        RGBA8Uint            = 0xd9,
+        RG16Unorm            = 0xda,
+        RG16Snorm            = 0xdb,
+        RG16Sint             = 0xdc,
+        RG16Uint             = 0xdd,
+        RG16Float            = 0xde,
+        BGR10A2Unorm         = 0xdf,
+        R11G11B10Float       = 0xe0,
+        R32Sint              = 0xe3,
+        R32Uint              = 0xe4,
+        R32Float             = 0xe5,
+        BGRX8Unorm           = 0xe6,
+        BGRX8Srgb            = 0xe7,
+        B5G6R5Unorm          = 0xe8,
+        BGR5A1Unorm          = 0xe9,
+        RG8Unorm             = 0xea,
+        RG8Snorm             = 0xeb,
+        RG8Sint              = 0xec,
+        RG8Uint              = 0xed,
+        R16Unorm             = 0xee,
+        R16Snorm             = 0xef,
+        R16Sint              = 0xf0,
+        R16Uint              = 0xf1,
+        R16Float             = 0xf2,
+        R8Unorm              = 0xf3,
+        R8Snorm              = 0xf4,
+        R8Sint               = 0xf5,
+        R8Uint               = 0xf6,
+        A8Unorm              = 0xf7,
+        BGR5X1Unorm          = 0xf8,
+        RGBX8Unorm           = 0xf9,
+        RGBX8Srgb            = 0xfa,
+        BGR5X1UnormUnknownFB = 0xfb,
+        BGR5X1UnormUnknownFC = 0xfc,
+        BGRX8UnormUnknownFD  = 0xfd,
+        BGRX8UnormUnknownFE  = 0xfe,
+        Y32UintUnknownFF     = 0xff
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalImage.cs
similarity index 61%
rename from Ryujinx.Graphics/Gal/GalTexture.cs
rename to Ryujinx.Graphics/Gal/GalImage.cs
index 2c1be65b..dc6f02e0 100644
--- a/Ryujinx.Graphics/Gal/GalTexture.cs
+++ b/Ryujinx.Graphics/Gal/GalImage.cs
@@ -1,25 +1,25 @@
 namespace Ryujinx.Graphics.Gal
 {
-    public struct GalTexture
+    public struct GalImage
     {
         public int Width;
         public int Height;
 
-        public GalTextureFormat Format;
+        public GalImageFormat Format;
 
         public GalTextureSource XSource;
         public GalTextureSource YSource;
         public GalTextureSource ZSource;
         public GalTextureSource WSource;
 
-        public GalTexture(
+        public GalImage(
             int              Width,
             int              Height,
-            GalTextureFormat Format,
-            GalTextureSource XSource,
-            GalTextureSource YSource,
-            GalTextureSource ZSource,
-            GalTextureSource WSource)
+            GalImageFormat   Format,
+            GalTextureSource XSource = GalTextureSource.Red,
+            GalTextureSource YSource = GalTextureSource.Green,
+            GalTextureSource ZSource = GalTextureSource.Blue,
+            GalTextureSource WSource = GalTextureSource.Alpha)
         {
             this.Width   = Width;
             this.Height  = Height;
diff --git a/Ryujinx.Graphics/Gal/GalImageFormat.cs b/Ryujinx.Graphics/Gal/GalImageFormat.cs
new file mode 100644
index 00000000..4e84067b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalImageFormat.cs
@@ -0,0 +1,204 @@
+namespace Ryujinx.Graphics.Gal
+{
+    //These are Vulkan-based enumerations, do not take them as Tegra values
+    public enum GalImageFormat
+    {
+        Undefined = 0,
+
+        R4G4_UNORM_PACK8 = 1,
+        R4G4B4A4_UNORM_PACK16 = 2,
+        B4G4R4A4_UNORM_PACK16 = 3,
+        R5G6B5_UNORM_PACK16 = 4,
+        B5G6R5_UNORM_PACK16 = 5,
+        R5G5B5A1_UNORM_PACK16 = 6,
+        B5G5R5A1_UNORM_PACK16 = 7,
+        A1R5G5B5_UNORM_PACK16 = 8,
+        R8_UNORM = 9,
+        R8_SNORM = 10,
+        R8_USCALED = 11,
+        R8_SSCALED = 12,
+        R8_UINT = 13,
+        R8_SINT = 14,
+        R8_SRGB = 15,
+        R8G8_UNORM = 16,
+        R8G8_SNORM = 17,
+        R8G8_USCALED = 18,
+        R8G8_SSCALED = 19,
+        R8G8_UINT = 20,
+        R8G8_SINT = 21,
+        R8G8_SRGB = 22,
+        R8G8B8_UNORM = 23,
+        R8G8B8_SNORM = 24,
+        R8G8B8_USCALED = 25,
+        R8G8B8_SSCALED = 26,
+        R8G8B8_UINT = 27,
+        R8G8B8_SINT = 28,
+        R8G8B8_SRGB = 29,
+        B8G8R8_UNORM = 30,
+        B8G8R8_SNORM = 31,
+        B8G8R8_USCALED = 32,
+        B8G8R8_SSCALED = 33,
+        B8G8R8_UINT = 34,
+        B8G8R8_SINT = 35,
+        B8G8R8_SRGB = 36,
+        R8G8B8A8_UNORM = 37,
+        R8G8B8A8_SNORM = 38,
+        R8G8B8A8_USCALED = 39,
+        R8G8B8A8_SSCALED = 40,
+        R8G8B8A8_UINT = 41,
+        R8G8B8A8_SINT = 42,
+        R8G8B8A8_SRGB = 43,
+        B8G8R8A8_UNORM = 44,
+        B8G8R8A8_SNORM = 45,
+        B8G8R8A8_USCALED = 46,
+        B8G8R8A8_SSCALED = 47,
+        B8G8R8A8_UINT = 48,
+        B8G8R8A8_SINT = 49,
+        B8G8R8A8_SRGB = 50,
+        A8B8G8R8_UNORM_PACK32 = 51,
+        A8B8G8R8_SNORM_PACK32 = 52,
+        A8B8G8R8_USCALED_PACK32 = 53,
+        A8B8G8R8_SSCALED_PACK32 = 54,
+        A8B8G8R8_UINT_PACK32 = 55,
+        A8B8G8R8_SINT_PACK32 = 56,
+        A8B8G8R8_SRGB_PACK32 = 57,
+        A2R10G10B10_UNORM_PACK32 = 58,
+        A2R10G10B10_SNORM_PACK32 = 59,
+        A2R10G10B10_USCALED_PACK32 = 60,
+        A2R10G10B10_SSCALED_PACK32 = 61,
+        A2R10G10B10_UINT_PACK32 = 62,
+        A2R10G10B10_SINT_PACK32 = 63,
+        A2B10G10R10_UNORM_PACK32 = 64,
+        A2B10G10R10_SNORM_PACK32 = 65,
+        A2B10G10R10_USCALED_PACK32 = 66,
+        A2B10G10R10_SSCALED_PACK32 = 67,
+        A2B10G10R10_UINT_PACK32 = 68,
+        A2B10G10R10_SINT_PACK32 = 69,
+        R16_UNORM = 70,
+        R16_SNORM = 71,
+        R16_USCALED = 72,
+        R16_SSCALED = 73,
+        R16_UINT = 74,
+        R16_SINT = 75,
+        R16_SFLOAT = 76,
+        R16G16_UNORM = 77,
+        R16G16_SNORM = 78,
+        R16G16_USCALED = 79,
+        R16G16_SSCALED = 80,
+        R16G16_UINT = 81,
+        R16G16_SINT = 82,
+        R16G16_SFLOAT = 83,
+        R16G16B16_UNORM = 84,
+        R16G16B16_SNORM = 85,
+        R16G16B16_USCALED = 86,
+        R16G16B16_SSCALED = 87,
+        R16G16B16_UINT = 88,
+        R16G16B16_SINT = 89,
+        R16G16B16_SFLOAT = 90,
+        R16G16B16A16_UNORM = 91,
+        R16G16B16A16_SNORM = 92,
+        R16G16B16A16_USCALED = 93,
+        R16G16B16A16_SSCALED = 94,
+        R16G16B16A16_UINT = 95,
+        R16G16B16A16_SINT = 96,
+        R16G16B16A16_SFLOAT = 97,
+        R32_UINT = 98,
+        R32_SINT = 99,
+        R32_SFLOAT = 100,
+        R32G32_UINT = 101,
+        R32G32_SINT = 102,
+        R32G32_SFLOAT = 103,
+        R32G32B32_UINT = 104,
+        R32G32B32_SINT = 105,
+        R32G32B32_SFLOAT = 106,
+        R32G32B32A32_UINT = 107,
+        R32G32B32A32_SINT = 108,
+        R32G32B32A32_SFLOAT = 109,
+        R64_UINT = 110,
+        R64_SINT = 111,
+        R64_SFLOAT = 112,
+        R64G64_UINT = 113,
+        R64G64_SINT = 114,
+        R64G64_SFLOAT = 115,
+        R64G64B64_UINT = 116,
+        R64G64B64_SINT = 117,
+        R64G64B64_SFLOAT = 118,
+        R64G64B64A64_UINT = 119,
+        R64G64B64A64_SINT = 120,
+        R64G64B64A64_SFLOAT = 121,
+        B10G11R11_UFLOAT_PACK32 = 122,
+        E5B9G9R9_UFLOAT_PACK32 = 123,
+        D16_UNORM = 124,
+        X8_D24_UNORM_PACK32 = 125,
+        D32_SFLOAT = 126,
+        S8_UINT = 127,
+        D16_UNORM_S8_UINT = 128,
+        D24_UNORM_S8_UINT = 129,
+        D32_SFLOAT_S8_UINT = 130,
+        BC1_RGB_UNORM_BLOCK = 131,
+        BC1_RGB_SRGB_BLOCK = 132,
+        BC1_RGBA_UNORM_BLOCK = 133,
+        BC1_RGBA_SRGB_BLOCK = 134,
+        BC2_UNORM_BLOCK = 135,
+        BC2_SRGB_BLOCK = 136,
+        BC3_UNORM_BLOCK = 137,
+        BC3_SRGB_BLOCK = 138,
+        BC4_UNORM_BLOCK = 139,
+        BC4_SNORM_BLOCK = 140,
+        BC5_UNORM_BLOCK = 141,
+        BC5_SNORM_BLOCK = 142,
+        BC6H_UFLOAT_BLOCK = 143,
+        BC6H_SFLOAT_BLOCK = 144,
+        BC7_UNORM_BLOCK = 145,
+        BC7_SRGB_BLOCK = 146,
+        ETC2_R8G8B8_UNORM_BLOCK = 147,
+        ETC2_R8G8B8_SRGB_BLOCK = 148,
+        ETC2_R8G8B8A1_UNORM_BLOCK = 149,
+        ETC2_R8G8B8A1_SRGB_BLOCK = 150,
+        ETC2_R8G8B8A8_UNORM_BLOCK = 151,
+        ETC2_R8G8B8A8_SRGB_BLOCK = 152,
+        EAC_R11_UNORM_BLOCK = 153,
+        EAC_R11_SNORM_BLOCK = 154,
+        EAC_R11G11_UNORM_BLOCK = 155,
+        EAC_R11G11_SNORM_BLOCK = 156,
+
+        ASTC_BEGIN = ASTC_4x4_UNORM_BLOCK,
+
+        ASTC_4x4_UNORM_BLOCK = 157,
+        ASTC_4x4_SRGB_BLOCK = 158,
+        ASTC_5x4_UNORM_BLOCK = 159,
+        ASTC_5x4_SRGB_BLOCK = 160,
+        ASTC_5x5_UNORM_BLOCK = 161,
+        ASTC_5x5_SRGB_BLOCK = 162,
+        ASTC_6x5_UNORM_BLOCK = 163,
+        ASTC_6x5_SRGB_BLOCK = 164,
+        ASTC_6x6_UNORM_BLOCK = 165,
+        ASTC_6x6_SRGB_BLOCK = 166,
+        ASTC_8x5_UNORM_BLOCK = 167,
+        ASTC_8x5_SRGB_BLOCK = 168,
+        ASTC_8x6_UNORM_BLOCK = 169,
+        ASTC_8x6_SRGB_BLOCK = 170,
+        ASTC_8x8_UNORM_BLOCK = 171,
+        ASTC_8x8_SRGB_BLOCK = 172,
+        ASTC_10x5_UNORM_BLOCK = 173,
+        ASTC_10x5_SRGB_BLOCK = 174,
+        ASTC_10x6_UNORM_BLOCK = 175,
+        ASTC_10x6_SRGB_BLOCK = 176,
+        ASTC_10x8_UNORM_BLOCK = 177,
+        ASTC_10x8_SRGB_BLOCK = 178,
+        ASTC_10x10_UNORM_BLOCK = 179,
+        ASTC_10x10_SRGB_BLOCK = 180,
+        ASTC_12x10_UNORM_BLOCK = 181,
+        ASTC_12x10_SRGB_BLOCK = 182,
+        ASTC_12x12_UNORM_BLOCK = 183,
+        ASTC_12x12_SRGB_BLOCK = 184,
+
+        ASTC_END = ASTC_12x12_SRGB_BLOCK,
+
+        REVERSED_BEGIN,
+
+        R4G4B4A4_UNORM_PACK16_REVERSED = REVERSED_BEGIN,
+
+        REVERSED_END
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
index 7e3e65e8..5ab7be89 100644
--- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs
+++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Gal
         R32          = 0xf,
         BC6H_SF16    = 0x10,
         BC6H_UF16    = 0x11,
+        A4B4G4R4     = 0x12,
         A1B5G5R5     = 0x14,
         B5G6R5       = 0x15,
         BC7U         = 0x17,
diff --git a/Ryujinx.Graphics/Gal/GalTextureType.cs b/Ryujinx.Graphics/Gal/GalTextureType.cs
new file mode 100644
index 00000000..f7dd16d1
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureType.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalTextureType
+    {
+        Snorm = 1,
+        Unorm = 2,
+        Sint = 3,
+        Uint = 4,
+        Snorm_Force_Fp16 = 5,
+        Unorm_Force_Fp16 = 6,
+        Float = 7
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalZetaFormat.cs b/Ryujinx.Graphics/Gal/GalZetaFormat.cs
new file mode 100644
index 00000000..759e3121
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalZetaFormat.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalZetaFormat
+    {
+        Z32Float          = 0x0a,
+        Z16Unorm          = 0x13,
+        S8Z24Unorm        = 0x14,
+        Z24X8Unorm        = 0x15,
+        Z24S8Unorm        = 0x16,
+        Z24C8Unorm        = 0x18,
+        Z32S8X24Float     = 0x19,
+        Z24X8S8C8X16Unorm = 0x1d,
+        Z32X8C8X16Float   = 0x1e,
+        Z32S8C8X16Float   = 0x1f
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
index c0287ef8..bce1981a 100644
--- a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
@@ -4,9 +4,13 @@ namespace Ryujinx.Graphics.Gal
 {
     public interface IGalFrameBuffer
     {
-        void Create(long Key, int Width, int Height);
+        void BindColor(long Key, int Attachment);
 
-        void Bind(long Key);
+        void UnbindColor(int Attachment);
+
+        void BindZeta(long Key);
+
+        void UnbindZeta();
 
         void BindTexture(long Key, int Index);
 
@@ -40,7 +44,6 @@ namespace Ryujinx.Graphics.Gal
             long             Key,
             int              Width,
             int              Height,
-            GalTextureFormat Format,
             byte[]           Buffer);
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/IGalRasterizer.cs b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
index 89e50b1f..a20b6f53 100644
--- a/Ryujinx.Graphics/Gal/IGalRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/IGalRasterizer.cs
@@ -9,6 +9,7 @@ namespace Ryujinx.Graphics.Gal
 
         void ClearBuffers(
             GalClearBufferFlags Flags,
+            int Attachment,
             float Red, float Green, float Blue, float Alpha,
             float Depth,
             int Stencil);
diff --git a/Ryujinx.Graphics/Gal/IGalTexture.cs b/Ryujinx.Graphics/Gal/IGalTexture.cs
index 2ab41199..292f59ef 100644
--- a/Ryujinx.Graphics/Gal/IGalTexture.cs
+++ b/Ryujinx.Graphics/Gal/IGalTexture.cs
@@ -5,9 +5,11 @@ namespace Ryujinx.Graphics.Gal
         void LockCache();
         void UnlockCache();
 
-        void Create(long Key, byte[] Data, GalTexture Texture);
+        void Create(long Key, byte[] Data, GalImage Image);
 
-        bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture);
+        void CreateFb(long Key, long Size, GalImage Image);
+
+        bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image);
 
         void Bind(long Key, int Index);
 
diff --git a/Ryujinx.Graphics/Gal/ImageFormatConverter.cs b/Ryujinx.Graphics/Gal/ImageFormatConverter.cs
new file mode 100644
index 00000000..2d20a8a0
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/ImageFormatConverter.cs
@@ -0,0 +1,263 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal
+{
+    public static class ImageFormatConverter
+    {
+        public static GalImageFormat ConvertTexture(
+            GalTextureFormat Format,
+            GalTextureType RType,
+            GalTextureType GType,
+            GalTextureType BType,
+            GalTextureType AType)
+        {
+            if (RType != GType || RType != BType || RType != AType)
+            {
+                throw new NotImplementedException("Per component types are not implemented");
+            }
+
+            GalTextureType Type = RType;
+
+            switch (Type)
+            {
+                case GalTextureType.Snorm:
+                    switch (Format)
+                    {
+                        case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SNORM;
+                        case GalTextureFormat.A8B8G8R8:     return GalImageFormat.A8B8G8R8_SNORM_PACK32;
+                        case GalTextureFormat.A2B10G10R10:  return GalImageFormat.A2B10G10R10_SNORM_PACK32;
+                        case GalTextureFormat.G8R8:         return GalImageFormat.R8G8_SNORM;
+                        case GalTextureFormat.R16:          return GalImageFormat.R16_SNORM;
+                        case GalTextureFormat.R8:           return GalImageFormat.R8_SNORM;
+                        case GalTextureFormat.BC4:          return GalImageFormat.BC4_SNORM_BLOCK;
+                        case GalTextureFormat.BC5:          return GalImageFormat.BC5_SNORM_BLOCK;
+                    }
+                    break;
+
+                case GalTextureType.Unorm:
+                    switch (Format)
+                    {
+                        case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_UNORM;
+                        case GalTextureFormat.A8B8G8R8:     return GalImageFormat.A8B8G8R8_UNORM_PACK32;
+                        case GalTextureFormat.A2B10G10R10:  return GalImageFormat.A2B10G10R10_UNORM_PACK32;
+                        case GalTextureFormat.A4B4G4R4:     return GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED;
+                        case GalTextureFormat.A1B5G5R5:     return GalImageFormat.A1R5G5B5_UNORM_PACK16;
+                        case GalTextureFormat.B5G6R5:       return GalImageFormat.B5G6R5_UNORM_PACK16;
+                        case GalTextureFormat.BC7U:         return GalImageFormat.BC7_UNORM_BLOCK;
+                        case GalTextureFormat.G8R8:         return GalImageFormat.R8G8_UNORM;
+                        case GalTextureFormat.R16:          return GalImageFormat.R16_UNORM;
+                        case GalTextureFormat.R8:           return GalImageFormat.R8_UNORM;
+                        case GalTextureFormat.BC1:          return GalImageFormat.BC1_RGBA_UNORM_BLOCK;
+                        case GalTextureFormat.BC2:          return GalImageFormat.BC2_UNORM_BLOCK;
+                        case GalTextureFormat.BC3:          return GalImageFormat.BC3_UNORM_BLOCK;
+                        case GalTextureFormat.BC4:          return GalImageFormat.BC4_UNORM_BLOCK;
+                        case GalTextureFormat.BC5:          return GalImageFormat.BC5_UNORM_BLOCK;
+                        case GalTextureFormat.Z24S8:        return GalImageFormat.D24_UNORM_S8_UINT;
+                        case GalTextureFormat.Astc2D4x4:    return GalImageFormat.ASTC_4x4_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D5x5:    return GalImageFormat.ASTC_5x5_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D6x6:    return GalImageFormat.ASTC_6x6_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D8x8:    return GalImageFormat.ASTC_8x8_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D10x10:  return GalImageFormat.ASTC_10x10_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D12x12:  return GalImageFormat.ASTC_12x12_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D5x4:    return GalImageFormat.ASTC_5x4_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D6x5:    return GalImageFormat.ASTC_6x5_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D8x6:    return GalImageFormat.ASTC_8x6_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D10x8:   return GalImageFormat.ASTC_10x8_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D12x10:  return GalImageFormat.ASTC_12x10_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D8x5:    return GalImageFormat.ASTC_8x5_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D10x5:   return GalImageFormat.ASTC_10x5_UNORM_BLOCK;
+                        case GalTextureFormat.Astc2D10x6:   return GalImageFormat.ASTC_10x6_UNORM_BLOCK;
+                    }
+                    break;
+
+                case GalTextureType.Sint:
+                    switch (Format)
+                    {
+                        case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_SINT;
+                        case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SINT;
+                        case GalTextureFormat.A8B8G8R8:     return GalImageFormat.A8B8G8R8_SINT_PACK32;
+                        case GalTextureFormat.A2B10G10R10:  return GalImageFormat.A2B10G10R10_SINT_PACK32;
+                        case GalTextureFormat.R32:          return GalImageFormat.R32_SINT;
+                        case GalTextureFormat.G8R8:         return GalImageFormat.R8G8_SINT;
+                        case GalTextureFormat.R16:          return GalImageFormat.R16_SINT;
+                        case GalTextureFormat.R8:           return GalImageFormat.R8_SINT;
+                    }
+                    break;
+
+                case GalTextureType.Uint:
+                    switch (Format)
+                    {
+                        case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_UINT;
+                        case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_UINT;
+                        case GalTextureFormat.A8B8G8R8:     return GalImageFormat.A8B8G8R8_UINT_PACK32;
+                        case GalTextureFormat.A2B10G10R10:  return GalImageFormat.A2B10G10R10_UINT_PACK32;
+                        case GalTextureFormat.R32:          return GalImageFormat.R32_UINT;
+                        case GalTextureFormat.G8R8:         return GalImageFormat.R8G8_UINT;
+                        case GalTextureFormat.R16:          return GalImageFormat.R16_UINT;
+                        case GalTextureFormat.R8:           return GalImageFormat.R8_UINT;
+                    }
+                    break;
+
+                case GalTextureType.Snorm_Force_Fp16:
+                    //TODO
+                    break;
+
+                case GalTextureType.Unorm_Force_Fp16:
+                    //TODO
+                    break;
+
+                case GalTextureType.Float:
+                    switch (Format)
+                    {
+                        case GalTextureFormat.R32G32B32A32: return GalImageFormat.R32G32B32A32_SFLOAT;
+                        case GalTextureFormat.R16G16B16A16: return GalImageFormat.R16G16B16A16_SFLOAT;
+                        case GalTextureFormat.R32:          return GalImageFormat.R32_SFLOAT;
+                        case GalTextureFormat.BC6H_SF16:    return GalImageFormat.BC6H_SFLOAT_BLOCK;
+                        case GalTextureFormat.BC6H_UF16:    return GalImageFormat.BC6H_UFLOAT_BLOCK;
+                        case GalTextureFormat.R16:          return GalImageFormat.R16_SFLOAT;
+                        case GalTextureFormat.BF10GF11RF11: return GalImageFormat.B10G11R11_UFLOAT_PACK32;
+                        case GalTextureFormat.ZF32:         return GalImageFormat.D32_SFLOAT;
+                    }
+                    break;
+            }
+
+            throw new NotImplementedException("0x" + Format.ToString("x2") + " " + Type.ToString());
+        }
+
+        public static GalImageFormat ConvertFrameBuffer(GalFrameBufferFormat Format)
+        {
+            switch (Format)
+            {
+                case GalFrameBufferFormat.R32Float:       return GalImageFormat.R32_SFLOAT;
+                case GalFrameBufferFormat.RGB10A2Unorm:   return GalImageFormat.A2B10G10R10_UNORM_PACK32;
+                case GalFrameBufferFormat.RGBA8Srgb:      return GalImageFormat.A8B8G8R8_SRGB_PACK32;
+                case GalFrameBufferFormat.RGBA16Float:    return GalImageFormat.R16G16B16A16_SFLOAT;
+                case GalFrameBufferFormat.R16Float:       return GalImageFormat.R16_SFLOAT;
+                case GalFrameBufferFormat.R8Unorm:        return GalImageFormat.R8_UNORM;
+                case GalFrameBufferFormat.RGBA8Unorm:     return GalImageFormat.A8B8G8R8_UNORM_PACK32;
+                case GalFrameBufferFormat.R11G11B10Float: return GalImageFormat.B10G11R11_UFLOAT_PACK32;
+                case GalFrameBufferFormat.RGBA32Float:    return GalImageFormat.R32G32B32A32_SFLOAT;
+                case GalFrameBufferFormat.RG16Snorm:      return GalImageFormat.R16G16_SNORM;
+                case GalFrameBufferFormat.RG16Float:      return GalImageFormat.R16G16_SFLOAT;
+                case GalFrameBufferFormat.RG8Snorm:       return GalImageFormat.R8_SNORM;
+                case GalFrameBufferFormat.RGBA8Snorm:     return GalImageFormat.A8B8G8R8_SNORM_PACK32;
+                case GalFrameBufferFormat.RG8Unorm:       return GalImageFormat.R8G8_UNORM;
+            }
+
+            throw new NotImplementedException(Format.ToString());
+        }
+
+        public static GalImageFormat ConvertZeta(GalZetaFormat Format)
+        {
+            switch (Format)
+            {
+                case GalZetaFormat.Z32Float:   return GalImageFormat.D32_SFLOAT;
+                case GalZetaFormat.S8Z24Unorm: return GalImageFormat.D24_UNORM_S8_UINT;
+                case GalZetaFormat.Z16Unorm:   return GalImageFormat.D16_UNORM;
+            }
+
+            throw new NotImplementedException(Format.ToString());
+        }
+
+        public static bool HasColor(GalImageFormat Format)
+        {
+            switch (Format)
+            {
+                case GalImageFormat.R32G32B32A32_SFLOAT:
+                case GalImageFormat.R32G32B32A32_SINT:
+                case GalImageFormat.R32G32B32A32_UINT:
+                case GalImageFormat.R16G16B16A16_SFLOAT:
+                case GalImageFormat.R16G16B16A16_SINT:
+                case GalImageFormat.R16G16B16A16_UINT:
+                case GalImageFormat.A8B8G8R8_SNORM_PACK32:
+                case GalImageFormat.A8B8G8R8_UNORM_PACK32:
+                case GalImageFormat.A8B8G8R8_SINT_PACK32:
+                case GalImageFormat.A8B8G8R8_UINT_PACK32:
+                case GalImageFormat.A2B10G10R10_SINT_PACK32:
+                case GalImageFormat.A2B10G10R10_SNORM_PACK32:
+                case GalImageFormat.A2B10G10R10_UINT_PACK32:
+                case GalImageFormat.A2B10G10R10_UNORM_PACK32:
+                case GalImageFormat.R32_SFLOAT:
+                case GalImageFormat.R32_SINT:
+                case GalImageFormat.R32_UINT:
+                case GalImageFormat.BC6H_SFLOAT_BLOCK:
+                case GalImageFormat.BC6H_UFLOAT_BLOCK:
+                case GalImageFormat.A1R5G5B5_UNORM_PACK16:
+                case GalImageFormat.B5G6R5_UNORM_PACK16:
+                case GalImageFormat.BC7_UNORM_BLOCK:
+                case GalImageFormat.R16G16_SFLOAT:
+                case GalImageFormat.R16G16_SINT:
+                case GalImageFormat.R16G16_SNORM:
+                case GalImageFormat.R16G16_UNORM:
+                case GalImageFormat.R8G8_SINT:
+                case GalImageFormat.R8G8_SNORM:
+                case GalImageFormat.R8G8_UINT:
+                case GalImageFormat.R8G8_UNORM:
+                case GalImageFormat.R16_SFLOAT:
+                case GalImageFormat.R16_SINT:
+                case GalImageFormat.R16_SNORM:
+                case GalImageFormat.R16_UINT:
+                case GalImageFormat.R16_UNORM:
+                case GalImageFormat.R8_SINT:
+                case GalImageFormat.R8_SNORM:
+                case GalImageFormat.R8_UINT:
+                case GalImageFormat.R8_UNORM:
+                case GalImageFormat.B10G11R11_UFLOAT_PACK32:
+                case GalImageFormat.BC1_RGBA_UNORM_BLOCK:
+                case GalImageFormat.BC2_UNORM_BLOCK:
+                case GalImageFormat.BC3_UNORM_BLOCK:
+                case GalImageFormat.BC4_UNORM_BLOCK:
+                case GalImageFormat.BC5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_4x4_UNORM_BLOCK:
+                case GalImageFormat.ASTC_5x5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_6x6_UNORM_BLOCK:
+                case GalImageFormat.ASTC_8x8_UNORM_BLOCK:
+                case GalImageFormat.ASTC_10x10_UNORM_BLOCK:
+                case GalImageFormat.ASTC_12x12_UNORM_BLOCK:
+                case GalImageFormat.ASTC_5x4_UNORM_BLOCK:
+                case GalImageFormat.ASTC_6x5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_8x6_UNORM_BLOCK:
+                case GalImageFormat.ASTC_10x8_UNORM_BLOCK:
+                case GalImageFormat.ASTC_12x10_UNORM_BLOCK:
+                case GalImageFormat.ASTC_8x5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_10x5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_10x6_UNORM_BLOCK:
+                case GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED:
+                    return true;
+
+                case GalImageFormat.D24_UNORM_S8_UINT:
+                case GalImageFormat.D32_SFLOAT:
+                case GalImageFormat.D16_UNORM:
+                    return true;
+            }
+
+            throw new NotImplementedException(Format.ToString());
+        }
+
+        public static bool HasDepth(GalImageFormat Format)
+        {
+            switch (Format)
+            {
+                case GalImageFormat.D24_UNORM_S8_UINT:
+                case GalImageFormat.D32_SFLOAT:
+                case GalImageFormat.D16_UNORM:
+                    return true;
+            }
+
+            //Depth formats are fewer than colors, so it's harder to miss one
+            //Instead of checking for individual formats, return false
+            return false;
+        }
+
+        public static bool HasStencil(GalImageFormat Format)
+        {
+            switch (Format)
+            {
+                case GalImageFormat.D24_UNORM_S8_UINT:
+                    return true;
+            }
+
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs
new file mode 100644
index 00000000..74f18dcd
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/ImageHandler.cs
@@ -0,0 +1,124 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class ImageHandler
+    {
+        //TODO: Use a variable value here
+        public const int MaxBpp = 16;
+
+        private static int CopyBuffer = 0;
+        private static int CopyBufferSize = 0;
+
+        public GalImage Image { get; private set; }
+
+        public int Width  => Image.Width;
+        public int Height => Image.Height;
+
+        public GalImageFormat Format => Image.Format;
+
+        public PixelInternalFormat InternalFormat { get; private set; }
+        public PixelFormat         PixelFormat    { get; private set; }
+        public PixelType           PixelType      { get; private set; }
+
+        public int Handle { get; private set; }
+
+        private bool Initialized;
+
+        public ImageHandler()
+        {
+            Handle = GL.GenTexture();
+        }
+
+        public ImageHandler(int Handle, GalImage Image)
+        {
+            this.Handle = Handle;
+
+            this.Image = Image;
+        }
+
+        public void EnsureSetup(GalImage Image)
+        {
+            if (Width  != Image.Width  ||
+                Height != Image.Height ||
+                Format != Image.Format ||
+                !Initialized)
+            {
+                (PixelInternalFormat InternalFormat, PixelFormat PixelFormat, PixelType PixelType) =
+                    OGLEnumConverter.GetImageFormat(Image.Format);
+
+                GL.BindTexture(TextureTarget.Texture2D, Handle);
+
+                if (Initialized)
+                {
+                    if (CopyBuffer == 0)
+                    {
+                        CopyBuffer = GL.GenBuffer();
+                    }
+
+                    int MaxWidth  = Math.Max(Image.Width, Width);
+                    int MaxHeight = Math.Max(Image.Height, Height);
+
+                    int CurrentSize = MaxWidth * MaxHeight * MaxBpp;
+
+                    GL.BindBuffer(BufferTarget.PixelPackBuffer, CopyBuffer);
+                    GL.BindBuffer(BufferTarget.PixelUnpackBuffer, CopyBuffer);
+
+                    if (CopyBufferSize < CurrentSize)
+                    {
+                        CopyBufferSize = CurrentSize;
+
+                        GL.BufferData(BufferTarget.PixelPackBuffer, CurrentSize, IntPtr.Zero, BufferUsageHint.StreamCopy);
+                    }
+
+                    GL.GetTexImage(TextureTarget.Texture2D, 0, this.PixelFormat, this.PixelType, IntPtr.Zero);
+
+                    GL.DeleteTexture(Handle);
+
+                    Handle = GL.GenTexture();
+
+                    GL.BindTexture(TextureTarget.Texture2D, Handle);
+                }
+
+                const int MinFilter = (int)TextureMinFilter.Linear;
+                const int MagFilter = (int)TextureMagFilter.Linear;
+
+                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter);
+                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter);
+
+                const int Level = 0;
+                const int Border = 0;
+
+                GL.TexImage2D(
+                    TextureTarget.Texture2D,
+                    Level,
+                    InternalFormat,
+                    Image.Width,
+                    Image.Height,
+                    Border,
+                    PixelFormat,
+                    PixelType,
+                    IntPtr.Zero);
+
+                if (Initialized)
+                {
+                    GL.BindBuffer(BufferTarget.PixelPackBuffer, 0);
+                    GL.BindBuffer(BufferTarget.PixelUnpackBuffer, 0);
+                }
+
+                this.Image = Image;
+
+                this.InternalFormat = InternalFormat;
+                this.PixelFormat = PixelFormat;
+                this.PixelType = PixelType;
+
+                Initialized = true;
+            }
+        }
+
+        public bool HasColor   { get => ImageFormatConverter.HasColor(Format); }
+        public bool HasDepth   { get => ImageFormatConverter.HasDepth(Format); }
+        public bool HasStencil { get => ImageFormatConverter.HasStencil(Format); }
+    }
+}
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs
index 50825541..4958b53b 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLConstBuffer.cs
@@ -36,12 +36,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         public void SetData(long Key, long Size, IntPtr HostAddress)
         {
-            if (!Cache.TryGetValue(Key, out OGLStreamBuffer Buffer))
+            if (Cache.TryGetValue(Key, out OGLStreamBuffer Buffer))
             {
-                throw new InvalidOperationException();
+                Buffer.SetData(Size, HostAddress);
             }
-
-            Buffer.SetData(Size, HostAddress);
         }
 
         public bool TryGetUbo(long Key, out int UboHandle)
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
index 3c42e5d3..e04a59d4 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
@@ -125,40 +125,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             throw new ArgumentException(nameof(Type));
         }
 
-        public static (PixelFormat, PixelType) GetTextureFormat(GalTextureFormat Format)
+        public static (PixelInternalFormat, PixelFormat, PixelType) GetImageFormat(GalImageFormat Format)
         {
             switch (Format)
             {
-                case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba,           PixelType.Float);
-                case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba,           PixelType.HalfFloat);
-                case GalTextureFormat.A8B8G8R8:     return (PixelFormat.Rgba,           PixelType.UnsignedByte);
-                case GalTextureFormat.A2B10G10R10:  return (PixelFormat.Rgba,           PixelType.UnsignedInt2101010Reversed);
-                case GalTextureFormat.R32:          return (PixelFormat.Red,            PixelType.Float);
-                case GalTextureFormat.A1B5G5R5:     return (PixelFormat.Rgba,           PixelType.UnsignedShort5551);
-                case GalTextureFormat.B5G6R5:       return (PixelFormat.Rgb,            PixelType.UnsignedShort565);
-                case GalTextureFormat.G8R8:         return (PixelFormat.Rg,             PixelType.UnsignedByte);
-                case GalTextureFormat.R16:          return (PixelFormat.Red,            PixelType.HalfFloat);
-                case GalTextureFormat.R8:           return (PixelFormat.Red,            PixelType.UnsignedByte);
-                case GalTextureFormat.ZF32:         return (PixelFormat.DepthComponent, PixelType.Float);
-                case GalTextureFormat.BF10GF11RF11: return (PixelFormat.Rgb,            PixelType.UnsignedInt10F11F11FRev);
-                case GalTextureFormat.Z24S8:        return (PixelFormat.DepthStencil,   PixelType.UnsignedInt248);
+                case GalImageFormat.R32G32B32A32_SFLOAT:      return (PixelInternalFormat.Rgba32f,      PixelFormat.Rgba,        PixelType.Float);
+                case GalImageFormat.R32G32B32A32_SINT:        return (PixelInternalFormat.Rgba32i,      PixelFormat.RgbaInteger, PixelType.Int);
+                case GalImageFormat.R32G32B32A32_UINT:        return (PixelInternalFormat.Rgba32ui,     PixelFormat.RgbaInteger, PixelType.UnsignedInt);
+                case GalImageFormat.R16G16B16A16_SFLOAT:      return (PixelInternalFormat.Rgba16f,      PixelFormat.Rgba,        PixelType.HalfFloat);
+                case GalImageFormat.R16G16B16A16_SINT:        return (PixelInternalFormat.Rgba16i,      PixelFormat.RgbaInteger, PixelType.Short);
+                case GalImageFormat.R16G16B16A16_UINT:        return (PixelInternalFormat.Rgba16ui,     PixelFormat.RgbaInteger, PixelType.UnsignedShort);
+                case GalImageFormat.A8B8G8R8_SNORM_PACK32:    return (PixelInternalFormat.Rgba8Snorm,   PixelFormat.Rgba,        PixelType.Byte);
+                case GalImageFormat.A8B8G8R8_UNORM_PACK32:    return (PixelInternalFormat.Rgba8,        PixelFormat.Rgba,        PixelType.UnsignedByte);
+                case GalImageFormat.A8B8G8R8_SINT_PACK32:     return (PixelInternalFormat.Rgba8i,       PixelFormat.RgbaInteger, PixelType.Byte);
+                case GalImageFormat.A8B8G8R8_UINT_PACK32:     return (PixelInternalFormat.Rgba8ui,      PixelFormat.RgbaInteger, PixelType.UnsignedByte);
+                case GalImageFormat.A8B8G8R8_SRGB_PACK32:     return (PixelInternalFormat.Srgb8Alpha8,  PixelFormat.Rgba,        PixelType.UnsignedByte);
+                case GalImageFormat.A2B10G10R10_UINT_PACK32:  return (PixelInternalFormat.Rgb10A2ui,    PixelFormat.RgbaInteger, PixelType.UnsignedInt2101010Reversed);
+                case GalImageFormat.A2B10G10R10_UNORM_PACK32: return (PixelInternalFormat.Rgb10A2,      PixelFormat.Rgba,        PixelType.UnsignedInt2101010Reversed);
+                case GalImageFormat.R32_SFLOAT:               return (PixelInternalFormat.R32f,         PixelFormat.Red,         PixelType.Float);
+                case GalImageFormat.R32_SINT:                 return (PixelInternalFormat.R32i,         PixelFormat.Red,         PixelType.Int);
+                case GalImageFormat.R32_UINT:                 return (PixelInternalFormat.R32ui,        PixelFormat.Red,         PixelType.UnsignedInt);
+                case GalImageFormat.A1R5G5B5_UNORM_PACK16:    return (PixelInternalFormat.Rgb5A1,       PixelFormat.Rgba,        PixelType.UnsignedShort5551);
+                case GalImageFormat.B5G6R5_UNORM_PACK16:      return (PixelInternalFormat.Rgba,         PixelFormat.Rgb,         PixelType.UnsignedShort565);
+                case GalImageFormat.R16G16_SFLOAT:            return (PixelInternalFormat.Rg16f,        PixelFormat.Rg,          PixelType.HalfFloat);
+                case GalImageFormat.R16G16_SINT:              return (PixelInternalFormat.Rg16i,        PixelFormat.RgInteger,   PixelType.Short);
+                case GalImageFormat.R16G16_SNORM:             return (PixelInternalFormat.Rg16Snorm,    PixelFormat.Rg,          PixelType.Byte);
+                case GalImageFormat.R16G16_UNORM:             return (PixelInternalFormat.Rg16,         PixelFormat.Rg,          PixelType.UnsignedShort);
+                case GalImageFormat.R8G8_SINT:                return (PixelInternalFormat.Rg8i,         PixelFormat.RgInteger,   PixelType.Byte);
+                case GalImageFormat.R8G8_SNORM:               return (PixelInternalFormat.Rg8Snorm,     PixelFormat.Rg,          PixelType.Byte);
+                case GalImageFormat.R8G8_UINT:                return (PixelInternalFormat.Rg8ui,        PixelFormat.RgInteger,   PixelType.UnsignedByte);
+                case GalImageFormat.R8G8_UNORM:               return (PixelInternalFormat.Rg8,          PixelFormat.Rg,          PixelType.UnsignedByte);
+                case GalImageFormat.R16_SFLOAT:               return (PixelInternalFormat.R16f,         PixelFormat.Red,         PixelType.HalfFloat);
+                case GalImageFormat.R16_SINT:                 return (PixelInternalFormat.R16i,         PixelFormat.RedInteger,  PixelType.Short);
+                case GalImageFormat.R16_SNORM:                return (PixelInternalFormat.R16Snorm,     PixelFormat.Red,         PixelType.Byte);
+                case GalImageFormat.R16_UINT:                 return (PixelInternalFormat.R16ui,        PixelFormat.RedInteger,  PixelType.UnsignedShort);
+                case GalImageFormat.R16_UNORM:                return (PixelInternalFormat.R16,          PixelFormat.Red,         PixelType.UnsignedShort);
+                case GalImageFormat.R8_SINT:                  return (PixelInternalFormat.R8i,          PixelFormat.RedInteger,  PixelType.Byte);
+                case GalImageFormat.R8_SNORM:                 return (PixelInternalFormat.R8Snorm,      PixelFormat.Red,         PixelType.Byte);
+                case GalImageFormat.R8_UINT:                  return (PixelInternalFormat.R8ui,         PixelFormat.RedInteger,  PixelType.UnsignedByte);
+                case GalImageFormat.R8_UNORM:                 return (PixelInternalFormat.R8,           PixelFormat.Red,         PixelType.UnsignedByte);
+                case GalImageFormat.B10G11R11_UFLOAT_PACK32:  return (PixelInternalFormat.R11fG11fB10f, PixelFormat.Rgb,         PixelType.UnsignedInt10F11F11FRev);
+
+                case GalImageFormat.R4G4B4A4_UNORM_PACK16_REVERSED: return (PixelInternalFormat.Rgba4, PixelFormat.Rgba, PixelType.UnsignedShort4444Reversed);
+
+                case GalImageFormat.D24_UNORM_S8_UINT: return (PixelInternalFormat.Depth24Stencil8,   PixelFormat.DepthStencil,   PixelType.UnsignedInt248);
+                case GalImageFormat.D32_SFLOAT:        return (PixelInternalFormat.DepthComponent32f, PixelFormat.DepthComponent, PixelType.Float);
+                case GalImageFormat.D16_UNORM:         return (PixelInternalFormat.DepthComponent16,  PixelFormat.DepthComponent, PixelType.UnsignedShort);
             }
 
             throw new NotImplementedException(Format.ToString());
         }
 
-        public static InternalFormat GetCompressedTextureFormat(GalTextureFormat Format)
+        public static InternalFormat GetCompressedImageFormat(GalImageFormat Format)
         {
             switch (Format)
             {
-                case GalTextureFormat.BC6H_UF16: return InternalFormat.CompressedRgbBptcUnsignedFloat;
-                case GalTextureFormat.BC6H_SF16: return InternalFormat.CompressedRgbBptcSignedFloat;
-                case GalTextureFormat.BC7U:      return InternalFormat.CompressedRgbaBptcUnorm;
-                case GalTextureFormat.BC1:       return InternalFormat.CompressedRgbaS3tcDxt1Ext;
-                case GalTextureFormat.BC2:       return InternalFormat.CompressedRgbaS3tcDxt3Ext;
-                case GalTextureFormat.BC3:       return InternalFormat.CompressedRgbaS3tcDxt5Ext;
-                case GalTextureFormat.BC4:       return InternalFormat.CompressedRedRgtc1;
-                case GalTextureFormat.BC5:       return InternalFormat.CompressedRgRgtc2;
+                case GalImageFormat.BC6H_UFLOAT_BLOCK:    return InternalFormat.CompressedRgbBptcUnsignedFloat;
+                case GalImageFormat.BC6H_SFLOAT_BLOCK:    return InternalFormat.CompressedRgbBptcSignedFloat;
+                case GalImageFormat.BC7_UNORM_BLOCK:      return InternalFormat.CompressedRgbaBptcUnorm;
+                case GalImageFormat.BC1_RGBA_UNORM_BLOCK: return InternalFormat.CompressedRgbaS3tcDxt1Ext;
+                case GalImageFormat.BC2_UNORM_BLOCK:      return InternalFormat.CompressedRgbaS3tcDxt3Ext;
+                case GalImageFormat.BC3_UNORM_BLOCK:      return InternalFormat.CompressedRgbaS3tcDxt5Ext;
+                case GalImageFormat.BC4_SNORM_BLOCK:      return InternalFormat.CompressedSignedRedRgtc1;
+                case GalImageFormat.BC4_UNORM_BLOCK:      return InternalFormat.CompressedRedRgtc1;
+                case GalImageFormat.BC5_SNORM_BLOCK:      return InternalFormat.CompressedSignedRgRgtc2;
+                case GalImageFormat.BC5_UNORM_BLOCK:      return InternalFormat.CompressedRgRgtc2;
             }
 
             throw new NotImplementedException(Format.ToString());
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
index 62f82495..e0f12e4e 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
@@ -1,10 +1,9 @@
 using OpenTK.Graphics.OpenGL;
 using System;
-using System.Collections.Generic;
 
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
-    public class OGLFrameBuffer : IGalFrameBuffer
+    class OGLFrameBuffer : IGalFrameBuffer
     {
         private struct Rect
         {
@@ -15,50 +14,38 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
             public Rect(int X, int Y, int Width, int Height)
             {
-                this.X      = X;
-                this.Y      = Y;
-                this.Width  = Width;
+                this.X = X;
+                this.Y = Y;
+                this.Width = Width;
                 this.Height = Height;
             }
         }
 
-        private class FrameBuffer
+        private static readonly DrawBuffersEnum[] DrawBuffers = new DrawBuffersEnum[]
         {
-            public int Width  { get; set; }
-            public int Height { get; set; }
-
-            public int Handle    { get; private set; }
-            public int RbHandle  { get; private set; }
-            public int TexHandle { get; private set; }
-
-            public FrameBuffer(int Width, int Height, bool HasRenderBuffer)
-            {
-                this.Width  = Width;
-                this.Height = Height;
-
-                Handle    = GL.GenFramebuffer();
-                TexHandle = GL.GenTexture();
-
-                if (HasRenderBuffer)
-                {
-                    RbHandle = GL.GenRenderbuffer();
-                }
-            }
-        }
+            DrawBuffersEnum.ColorAttachment0,
+            DrawBuffersEnum.ColorAttachment1,
+            DrawBuffersEnum.ColorAttachment2,
+            DrawBuffersEnum.ColorAttachment3,
+            DrawBuffersEnum.ColorAttachment4,
+            DrawBuffersEnum.ColorAttachment5,
+            DrawBuffersEnum.ColorAttachment6,
+            DrawBuffersEnum.ColorAttachment7,
+        };
 
         private const int NativeWidth  = 1280;
         private const int NativeHeight = 720;
 
-        private Dictionary<long, FrameBuffer> Fbs;
+        private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8_UNORM_PACK32;
+
+        private OGLTexture Texture;
+
+        private ImageHandler RawTex;
+        private ImageHandler ReadTex;
 
         private Rect Viewport;
         private Rect Window;
 
-        private FrameBuffer CurrFb;
-        private FrameBuffer CurrReadFb;
-
-        private FrameBuffer RawFb;
-
         private bool FlipX;
         private bool FlipY;
 
@@ -67,111 +54,144 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         private int CropRight;
         private int CropBottom;
 
-        public OGLFrameBuffer()
+        //This framebuffer is used to attach guest rendertargets,
+        //think of it as a dummy OpenGL VAO
+        private int DummyFrameBuffer;
+
+        //These framebuffers are used to blit images
+        private int SrcFb;
+        private int DstFb;
+
+        //Holds current attachments, used to avoid unnecesary calls to OpenGL
+        private int[] ColorAttachments;
+
+        private int DepthAttachment;
+        private int StencilAttachment;
+
+        public OGLFrameBuffer(OGLTexture Texture)
         {
-            Fbs = new Dictionary<long, FrameBuffer>();
+            ColorAttachments = new int[8];
+
+            this.Texture = Texture;
         }
 
-        public void Create(long Key, int Width, int Height)
+        public void BindColor(long Key, int Attachment)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
             {
-                if (Fb.Width  != Width ||
-                    Fb.Height != Height)
-                {
-                    SetupTexture(Fb.TexHandle, Width, Height);
+                EnsureFrameBuffer();
 
-                    Fb.Width  = Width;
-                    Fb.Height = Height;
-                }
-
-                return;
+                Attach(ref ColorAttachments[Attachment], Tex.Handle, FramebufferAttachment.ColorAttachment0 + Attachment);
+            }
+            else
+            {
+                UnbindColor(Attachment);
             }
-
-            Fb = new FrameBuffer(Width, Height, true);
-
-            SetupTexture(Fb.TexHandle, Width, Height);
-
-            GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle);
-
-            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fb.RbHandle);
-
-            GL.RenderbufferStorage(
-                RenderbufferTarget.Renderbuffer,
-                RenderbufferStorage.Depth24Stencil8,
-                Width,
-                Height);
-
-            GL.FramebufferRenderbuffer(
-                FramebufferTarget.Framebuffer,
-                FramebufferAttachment.DepthStencilAttachment,
-                RenderbufferTarget.Renderbuffer,
-                Fb.RbHandle);
-
-            GL.FramebufferTexture(
-                FramebufferTarget.Framebuffer,
-                FramebufferAttachment.ColorAttachment0,
-                Fb.TexHandle,
-                0);
-
-            GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
-
-            Fbs.Add(Key, Fb);
         }
 
-        public void Bind(long Key)
+        public void UnbindColor(int Attachment)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
-            {
-                GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fb.Handle);
+            EnsureFrameBuffer();
 
-                CurrFb = Fb;
+            Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment);
+        }
+        
+        public void BindZeta(long Key)
+        {
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
+            {
+                EnsureFrameBuffer();
+
+                if (Tex.HasDepth && Tex.HasStencil)
+                {
+                    if (DepthAttachment   != Tex.Handle ||
+                        StencilAttachment != Tex.Handle)
+                    {
+                        GL.FramebufferTexture(
+                            FramebufferTarget.DrawFramebuffer,
+                            FramebufferAttachment.DepthStencilAttachment,
+                            Tex.Handle,
+                            0);
+
+                        DepthAttachment = Tex.Handle;
+
+                        StencilAttachment = Tex.Handle;
+                    }
+                }
+                else if (Tex.HasDepth)
+                {
+                    Attach(ref DepthAttachment, Tex.Handle, FramebufferAttachment.DepthAttachment);
+
+                    Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment);
+                }
+                else if (Tex.HasStencil)
+                {
+                    Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment);
+
+                    Attach(ref StencilAttachment, Tex.Handle, FramebufferAttachment.StencilAttachment);
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
+            }
+            else
+            {
+                UnbindZeta();
+            }
+        }
+
+        public void UnbindZeta()
+        {
+            EnsureFrameBuffer();
+
+            if (DepthAttachment   != 0 ||
+                StencilAttachment != 0)
+            {
+                GL.FramebufferTexture(
+                    FramebufferTarget.DrawFramebuffer,
+                    FramebufferAttachment.DepthStencilAttachment,
+                    0,
+                    0);
+
+                DepthAttachment = 0;
+
+                StencilAttachment = 0;
             }
         }
 
         public void BindTexture(long Key, int Index)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
             {
                 GL.ActiveTexture(TextureUnit.Texture0 + Index);
 
-                GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle);
+                GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
             }
         }
 
         public void Set(long Key)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
             {
-                CurrReadFb = Fb;
+                ReadTex = Tex;
             }
         }
 
         public void Set(byte[] Data, int Width, int Height)
         {
-            if (RawFb == null)
+            if (RawTex == null)
             {
-                CreateRawFb(Width, Height);
+                RawTex = new ImageHandler();
             }
 
-            if (RawFb.Width  != Width ||
-                RawFb.Height != Height)
-            {
-                SetupTexture(RawFb.TexHandle, Width, Height);
+            RawTex.EnsureSetup(new GalImage(Width, Height, RawFormat));
 
-                RawFb.Width  = Width;
-                RawFb.Height = Height;
-            }
+            GL.BindTexture(TextureTarget.Texture2D, RawTex.Handle);
 
-            GL.ActiveTexture(TextureUnit.Texture0);
+            GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, RawTex.PixelFormat, RawTex.PixelType, Data);
 
-            GL.BindTexture(TextureTarget.Texture2D, RawFb.TexHandle);
-
-            (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
-
-            GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, Format, Type, Data);
-
-            CurrReadFb = RawFb;
+            ReadTex = RawTex;
         }
 
         public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom)
@@ -208,60 +228,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         public void Render()
         {
-            if (CurrReadFb != null)
+            if (ReadTex == null)
             {
-                int SrcX0, SrcX1, SrcY0, SrcY1;
-
-                if (CropLeft == 0 && CropRight == 0)
-                {
-                    SrcX0 = 0;
-                    SrcX1 = CurrReadFb.Width;
-                }
-                else
-                {
-                    SrcX0 = CropLeft;
-                    SrcX1 = CropRight;
-                }
-
-                if (CropTop == 0 && CropBottom == 0)
-                {
-                    SrcY0 = 0;
-                    SrcY1 = CurrReadFb.Height;
-                }
-                else
-                {
-                    SrcY0 = CropTop;
-                    SrcY1 = CropBottom;
-                }
-
-                float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth)  / ((float)NativeHeight * Window.Width));
-                float RatioY = MathF.Min(1f, (Window.Width  * (float)NativeHeight) / ((float)NativeWidth  * Window.Height));
-
-                int DstWidth  = (int)(Window.Width  * RatioX);
-                int DstHeight = (int)(Window.Height * RatioY);
-
-                int DstPaddingX = (Window.Width  - DstWidth)  / 2;
-                int DstPaddingY = (Window.Height - DstHeight) / 2;
-
-                int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
-                int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
-
-                int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
-                int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
-
-                GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
-
-                GL.Viewport(0, 0, Window.Width, Window.Height);
-
-                GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrReadFb.Handle);
-
-                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
-
-                GL.BlitFramebuffer(
-                    SrcX0, SrcY0, SrcX1, SrcY1,
-                    DstX0, DstY0, DstX1, DstY1,
-                    ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
+                return;
             }
+
+            int SrcX0, SrcX1, SrcY0, SrcY1;
+
+            if (CropLeft == 0 && CropRight == 0)
+            {
+                SrcX0 = 0;
+                SrcX1 = ReadTex.Width;
+            }
+            else
+            {
+                SrcX0 = CropLeft;
+                SrcX1 = CropRight;
+            }
+
+            if (CropTop == 0 && CropBottom == 0)
+            {
+                SrcY0 = 0;
+                SrcY1 = ReadTex.Height;
+            }
+            else
+            {
+                SrcY0 = CropTop;
+                SrcY1 = CropBottom;
+            }
+
+            float RatioX = MathF.Min(1f, (Window.Height * (float)NativeWidth)  / ((float)NativeHeight * Window.Width));
+            float RatioY = MathF.Min(1f, (Window.Width  * (float)NativeHeight) / ((float)NativeWidth  * Window.Height));
+
+            int DstWidth  = (int)(Window.Width  * RatioX);
+            int DstHeight = (int)(Window.Height * RatioY);
+
+            int DstPaddingX = (Window.Width  - DstWidth)  / 2;
+            int DstPaddingY = (Window.Height - DstHeight) / 2;
+
+            int DstX0 = FlipX ? Window.Width - DstPaddingX : DstPaddingX;
+            int DstX1 = FlipX ? DstPaddingX : Window.Width - DstPaddingX;
+
+            int DstY0 = FlipY ? DstPaddingY : Window.Height - DstPaddingY;
+            int DstY1 = FlipY ? Window.Height - DstPaddingY : DstPaddingY;
+
+            if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
+
+            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, 0);
+
+            GL.Viewport(0, 0, Window.Width, Window.Height);
+
+            GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
+
+            GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0);
+
+            GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+            GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+
+            GL.Clear(ClearBufferMask.ColorBufferBit);
+
+            GL.BlitFramebuffer(
+                SrcX0, SrcY0, SrcX1, SrcY1,
+                DstX0, DstY0, DstX1, DstY1,
+                ClearBufferMask.ColorBufferBit, BlitFramebufferFilter.Linear);
+
+            EnsureFrameBuffer();
         }
 
         public void Copy(
@@ -276,39 +307,80 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             int  DstX1,
             int  DstY1)
         {
-            if (Fbs.TryGetValue(SrcKey, out FrameBuffer SrcFb) &&
-                Fbs.TryGetValue(DstKey, out FrameBuffer DstFb))
+            if (Texture.TryGetImage(SrcKey, out ImageHandler SrcTex) &&
+                Texture.TryGetImage(DstKey, out ImageHandler DstTex))
             {
-                GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb.Handle);
-                GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb.Handle);
+                if (SrcTex.HasColor != DstTex.HasColor ||
+                    SrcTex.HasDepth != DstTex.HasDepth ||
+                    SrcTex.HasStencil != DstTex.HasStencil)
+                {
+                    throw new NotImplementedException();
+                }
 
-                GL.Clear(ClearBufferMask.ColorBufferBit);
-
-                GL.BlitFramebuffer(
-                    SrcX0, SrcY0, SrcX1, SrcY1,
-                    DstX0, DstY0, DstX1, DstY1,
-                    ClearBufferMask.ColorBufferBit,
-                    BlitFramebufferFilter.Linear);
+                if (SrcTex.HasColor)
+                {
+                    CopyTextures(
+                        SrcX0, SrcY0, SrcX1, SrcY1,
+                        DstX0, DstY0, DstX1, DstY1,
+                        SrcTex.Handle,
+                        DstTex.Handle,
+                        FramebufferAttachment.ColorAttachment0,
+                        ClearBufferMask.ColorBufferBit,
+                        true);
+                }
+                else if (SrcTex.HasDepth && SrcTex.HasStencil)
+                {
+                    CopyTextures(
+                        SrcX0, SrcY0, SrcX1, SrcY1,
+                        DstX0, DstY0, DstX1, DstY1,
+                        SrcTex.Handle,
+                        DstTex.Handle,
+                        FramebufferAttachment.DepthStencilAttachment,
+                        ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit,
+                        false);
+                }
+                else if (SrcTex.HasDepth)
+                {
+                    CopyTextures(
+                        SrcX0, SrcY0, SrcX1, SrcY1,
+                        DstX0, DstY0, DstX1, DstY1,
+                        SrcTex.Handle,
+                        DstTex.Handle,
+                        FramebufferAttachment.DepthAttachment,
+                        ClearBufferMask.DepthBufferBit,
+                        false);
+                }
+                else if (SrcTex.HasStencil)
+                {
+                    CopyTextures(
+                        SrcX0, SrcY0, SrcX1, SrcY1,
+                        DstX0, DstY0, DstX1, DstY1,
+                        SrcTex.Handle,
+                        DstTex.Handle,
+                        FramebufferAttachment.StencilAttachment,
+                        ClearBufferMask.StencilBufferBit,
+                        false);
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
             }
-}
+        }
 
         public void GetBufferData(long Key, Action<byte[]> Callback)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
             {
-                GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, Fb.Handle);
+                byte[] Data = new byte[Tex.Width * Tex.Height * ImageHandler.MaxBpp];
 
-                byte[] Data = new byte[Fb.Width * Fb.Height * 4];
+                GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
 
-                (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
-
-                GL.ReadPixels(
+                GL.GetTexImage(
+                    TextureTarget.Texture2D,
                     0,
-                    0,
-                    Fb.Width,
-                    Fb.Height,
-                    Format,
-                    Type,
+                    Tex.PixelFormat,
+                    Tex.PixelType,
                     Data);
 
                 Callback(Data);
@@ -319,83 +391,101 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             long             Key,
             int              Width,
             int              Height,
-            GalTextureFormat Format,
             byte[]           Buffer)
         {
-            if (Fbs.TryGetValue(Key, out FrameBuffer Fb))
+            if (Texture.TryGetImage(Key, out ImageHandler Tex))
             {
-                GL.BindTexture(TextureTarget.Texture2D, Fb.TexHandle);
+                GL.BindTexture(TextureTarget.Texture2D, Tex.Handle);
 
                 const int Level  = 0;
                 const int Border = 0;
 
-                const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
-
-                (PixelFormat GlFormat, PixelType Type) = OGLEnumConverter.GetTextureFormat(Format);
-
                 GL.TexImage2D(
                     TextureTarget.Texture2D,
                     Level,
-                    InternalFmt,
+                    Tex.InternalFormat,
                     Width,
                     Height,
                     Border,
-                    GlFormat,
-                    Type,
+                    Tex.PixelFormat,
+                    Tex.PixelType,
                     Buffer);
             }
         }
 
-        private void CreateRawFb(int Width, int Height)
+        private void EnsureFrameBuffer()
         {
-            if (RawFb == null)
+            if (DummyFrameBuffer == 0)
             {
-                RawFb = new FrameBuffer(Width, Height, false);
+                DummyFrameBuffer = GL.GenFramebuffer();
+            }
 
-                SetupTexture(RawFb.TexHandle, Width, Height);
+            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
 
-                RawFb.Width = Width;
-                RawFb.Height = Height;
-
-                GL.BindFramebuffer(FramebufferTarget.Framebuffer, RawFb.Handle);
+            GL.DrawBuffers(8, DrawBuffers);
+        }
 
+        private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment)
+        {
+            if (OldHandle != NewHandle)
+            {
                 GL.FramebufferTexture(
-                    FramebufferTarget.Framebuffer,
-                    FramebufferAttachment.ColorAttachment0,
-                    RawFb.TexHandle,
+                    FramebufferTarget.DrawFramebuffer,
+                    FbAttachment,
+                    NewHandle,
                     0);
 
-                GL.Viewport(0, 0, Width, Height);
+                OldHandle = NewHandle;
             }
         }
 
-        private void SetupTexture(int Handle, int Width, int Height)
+        private void CopyTextures(
+            int SrcX0,
+            int SrcY0,
+            int SrcX1,
+            int SrcY1,
+            int DstX0,
+            int DstY0,
+            int DstX1,
+            int DstY1,
+            int SrcTexture,
+            int DstTexture,
+            FramebufferAttachment Attachment,
+            ClearBufferMask Mask,
+            bool Color)
         {
-            GL.BindTexture(TextureTarget.Texture2D, Handle);
+            if (SrcFb == 0) SrcFb = GL.GenFramebuffer();
+            if (DstFb == 0) DstFb = GL.GenFramebuffer();
 
-            const int MinFilter = (int)TextureMinFilter.Linear;
-            const int MagFilter = (int)TextureMagFilter.Linear;
+            GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, SrcFb);
+            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DstFb);
 
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter);
+            GL.FramebufferTexture(
+                FramebufferTarget.ReadFramebuffer,
+                Attachment,
+                SrcTexture,
+                0);
 
-            (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
+            GL.FramebufferTexture(
+                FramebufferTarget.DrawFramebuffer,
+                Attachment,
+                DstTexture,
+                0);
 
-            const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
+            if (Color)
+            {
+                GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+            }
 
-            const int Level  = 0;
-            const int Border = 0;
+            GL.Clear(Mask);
 
-            GL.TexImage2D(
-                TextureTarget.Texture2D,
-                Level,
-                InternalFmt,
-                Width,
-                Height,
-                Border,
-                Format,
-                Type,
-                IntPtr.Zero);
+            GL.BlitFramebuffer(
+                SrcX0, SrcY0, SrcX1, SrcY1,
+                DstX0, DstY0, DstX1, DstY1,
+                Mask,
+                Color ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
+
+            EnsureFrameBuffer();
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
index b6e97454..45106692 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
@@ -3,7 +3,7 @@ using System;
 
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
-    public class OGLRasterizer : IGalRasterizer
+    class OGLRasterizer : IGalRasterizer
     {
         private int[] VertexBuffers;
 
@@ -44,36 +44,29 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         public void ClearBuffers(
             GalClearBufferFlags Flags,
+            int Attachment,
             float Red, float Green, float Blue, float Alpha,
             float Depth,
             int Stencil)
         {
-            ClearBufferMask Mask = ClearBufferMask.ColorBufferBit;
-
             GL.ColorMask(
                 Flags.HasFlag(GalClearBufferFlags.ColorRed),
                 Flags.HasFlag(GalClearBufferFlags.ColorGreen),
                 Flags.HasFlag(GalClearBufferFlags.ColorBlue),
                 Flags.HasFlag(GalClearBufferFlags.ColorAlpha));
 
+            GL.ClearBuffer(ClearBuffer.Color, Attachment, new float[] { Red, Green, Blue, Alpha });
+
             if (Flags.HasFlag(GalClearBufferFlags.Depth))
             {
-                Mask |= ClearBufferMask.DepthBufferBit;
+                GL.ClearBuffer(ClearBuffer.Depth, 0, ref Depth);
             }
 
             if (Flags.HasFlag(GalClearBufferFlags.Stencil))
             {
-                Mask |= ClearBufferMask.StencilBufferBit;
+                GL.ClearBuffer(ClearBuffer.Stencil, 0, ref Stencil);
             }
 
-            GL.ClearColor(Red, Green, Blue, Alpha);
-
-            GL.ClearDepth(Depth);
-
-            GL.ClearStencil(Stencil);
-
-            GL.Clear(Mask);
-
             GL.ColorMask(true, true, true, true);
         }
 
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs
index b0f6da45..985f1086 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderer.cs
@@ -23,7 +23,9 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             Buffer = new OGLConstBuffer();
 
-            FrameBuffer = new OGLFrameBuffer();
+            Texture = new OGLTexture();
+
+            FrameBuffer = new OGLFrameBuffer(Texture as OGLTexture);
 
             Rasterizer = new OGLRasterizer();
 
@@ -31,8 +33,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
             Pipeline = new OGLPipeline(Buffer as OGLConstBuffer, Rasterizer as OGLRasterizer, Shader as OGLShader);
 
-            Texture = new OGLTexture();
-
             ActionsQueue = new ConcurrentQueue<Action>();
         }
 
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs
index ac30e6fd..e4d4bd64 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs
@@ -4,26 +4,13 @@ using System;
 
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
-    public class OGLTexture : IGalTexture
+    class OGLTexture : IGalTexture
     {
-        private class TCE
-        {
-            public int Handle;
-
-            public GalTexture Texture;
-
-            public TCE(int Handle, GalTexture Texture)
-            {
-                this.Handle  = Handle;
-                this.Texture = Texture;
-            }
-        }
-
-        private OGLCachedResource<TCE> TextureCache;
+        private OGLCachedResource<ImageHandler> TextureCache;
 
         public OGLTexture()
         {
-            TextureCache = new OGLCachedResource<TCE>(DeleteTexture);
+            TextureCache = new OGLCachedResource<ImageHandler>(DeleteTexture);
         }
 
         public void LockCache()
@@ -36,73 +23,71 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             TextureCache.Unlock();
         }
 
-        private static void DeleteTexture(TCE CachedTexture)
+        private static void DeleteTexture(ImageHandler CachedImage)
         {
-            GL.DeleteTexture(CachedTexture.Handle);
+            GL.DeleteTexture(CachedImage.Handle);
         }
 
-        public void Create(long Key, byte[] Data, GalTexture Texture)
+        public void Create(long Key, byte[] Data, GalImage Image)
         {
             int Handle = GL.GenTexture();
 
-            TextureCache.AddOrUpdate(Key, new TCE(Handle, Texture), (uint)Data.Length);
+            TextureCache.AddOrUpdate(Key, new ImageHandler(Handle, Image), (uint)Data.Length);
 
             GL.BindTexture(TextureTarget.Texture2D, Handle);
 
             const int Level  = 0; //TODO: Support mipmap textures.
             const int Border = 0;
 
-            if (IsCompressedTextureFormat(Texture.Format))
+            if (IsCompressedTextureFormat(Image.Format))
             {
-                InternalFormat InternalFmt = OGLEnumConverter.GetCompressedTextureFormat(Texture.Format);
+                InternalFormat InternalFmt = OGLEnumConverter.GetCompressedImageFormat(Image.Format);
 
                 GL.CompressedTexImage2D(
                     TextureTarget.Texture2D,
                     Level,
                     InternalFmt,
-                    Texture.Width,
-                    Texture.Height,
+                    Image.Width,
+                    Image.Height,
                     Border,
                     Data.Length,
                     Data);
             }
             else
             {
-                if (Texture.Format >= GalTextureFormat.Astc2D4x4)
+                if (Image.Format >= GalImageFormat.ASTC_BEGIN && Image.Format <= GalImageFormat.ASTC_END)
                 {
-                    int TextureBlockWidth  = GetAstcBlockWidth(Texture.Format);
-                    int TextureBlockHeight = GetAstcBlockHeight(Texture.Format);
+                    int TextureBlockWidth  = GetAstcBlockWidth(Image.Format);
+                    int TextureBlockHeight = GetAstcBlockHeight(Image.Format);
 
                     Data = ASTCDecoder.DecodeToRGBA8888(
                         Data,
                         TextureBlockWidth,
                         TextureBlockHeight, 1,
-                        Texture.Width,
-                        Texture.Height, 1);
+                        Image.Width,
+                        Image.Height, 1);
 
-                    Texture.Format = GalTextureFormat.A8B8G8R8;
+                    Image.Format = GalImageFormat.A8B8G8R8_UNORM_PACK32;
                 }
 
-                const PixelInternalFormat InternalFmt = PixelInternalFormat.Rgba;
-
-                (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(Texture.Format);
+                (PixelInternalFormat InternalFormat, PixelFormat Format, PixelType Type) = OGLEnumConverter.GetImageFormat(Image.Format);
 
                 GL.TexImage2D(
                     TextureTarget.Texture2D,
                     Level,
-                    InternalFmt,
-                    Texture.Width,
-                    Texture.Height,
+                    InternalFormat,
+                    Image.Width,
+                    Image.Height,
                     Border,
                     Format,
                     Type,
                     Data);
             }
 
-            int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Texture.XSource);
-            int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Texture.YSource);
-            int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Texture.ZSource);
-            int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Texture.WSource);
+            int SwizzleR = (int)OGLEnumConverter.GetTextureSwizzle(Image.XSource);
+            int SwizzleG = (int)OGLEnumConverter.GetTextureSwizzle(Image.YSource);
+            int SwizzleB = (int)OGLEnumConverter.GetTextureSwizzle(Image.ZSource);
+            int SwizzleA = (int)OGLEnumConverter.GetTextureSwizzle(Image.WSource);
 
             GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleR, SwizzleR);
             GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleG, SwizzleG);
@@ -110,76 +95,100 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureSwizzleA, SwizzleA);
         }
 
-        private static int GetAstcBlockWidth(GalTextureFormat Format)
+        public void CreateFb(long Key, long Size, GalImage Image)
+        {
+            if (!TryGetImage(Key, out ImageHandler CachedImage))
+            {
+                CachedImage = new ImageHandler();
+
+                TextureCache.AddOrUpdate(Key, CachedImage, Size);
+            }
+
+            CachedImage.EnsureSetup(Image);
+        }
+
+        public bool TryGetImage(long Key, out ImageHandler CachedImage)
+        {
+            if (TextureCache.TryGetValue(Key, out CachedImage))
+            {
+                return true;
+            }
+
+            CachedImage = null;
+
+            return false;
+        }
+
+        private static int GetAstcBlockWidth(GalImageFormat Format)
         {
             switch (Format)
             {
-                case GalTextureFormat.Astc2D4x4:   return 4;
-                case GalTextureFormat.Astc2D5x5:   return 5;
-                case GalTextureFormat.Astc2D6x6:   return 6;
-                case GalTextureFormat.Astc2D8x8:   return 8;
-                case GalTextureFormat.Astc2D10x10: return 10;
-                case GalTextureFormat.Astc2D12x12: return 12;
-                case GalTextureFormat.Astc2D5x4:   return 5;
-                case GalTextureFormat.Astc2D6x5:   return 6;
-                case GalTextureFormat.Astc2D8x6:   return 8;
-                case GalTextureFormat.Astc2D10x8:  return 10;
-                case GalTextureFormat.Astc2D12x10: return 12;
-                case GalTextureFormat.Astc2D8x5:   return 8;
-                case GalTextureFormat.Astc2D10x5:  return 10;
-                case GalTextureFormat.Astc2D10x6:  return 10;
+                case GalImageFormat.ASTC_4x4_UNORM_BLOCK:   return 4;
+                case GalImageFormat.ASTC_5x5_UNORM_BLOCK:   return 5;
+                case GalImageFormat.ASTC_6x6_UNORM_BLOCK:   return 6;
+                case GalImageFormat.ASTC_8x8_UNORM_BLOCK:   return 8;
+                case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10;
+                case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12;
+                case GalImageFormat.ASTC_5x4_UNORM_BLOCK:   return 5;
+                case GalImageFormat.ASTC_6x5_UNORM_BLOCK:   return 6;
+                case GalImageFormat.ASTC_8x6_UNORM_BLOCK:   return 8;
+                case GalImageFormat.ASTC_10x8_UNORM_BLOCK:  return 10;
+                case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 12;
+                case GalImageFormat.ASTC_8x5_UNORM_BLOCK:   return 8;
+                case GalImageFormat.ASTC_10x5_UNORM_BLOCK:  return 10;
+                case GalImageFormat.ASTC_10x6_UNORM_BLOCK:  return 10;
             }
 
             throw new ArgumentException(nameof(Format));
         }
 
-        private static int GetAstcBlockHeight(GalTextureFormat Format)
+        private static int GetAstcBlockHeight(GalImageFormat Format)
         {
             switch (Format)
             {
-                case GalTextureFormat.Astc2D4x4:   return 4;
-                case GalTextureFormat.Astc2D5x5:   return 5;
-                case GalTextureFormat.Astc2D6x6:   return 6;
-                case GalTextureFormat.Astc2D8x8:   return 8;
-                case GalTextureFormat.Astc2D10x10: return 10;
-                case GalTextureFormat.Astc2D12x12: return 12;
-                case GalTextureFormat.Astc2D5x4:   return 4;
-                case GalTextureFormat.Astc2D6x5:   return 5;
-                case GalTextureFormat.Astc2D8x6:   return 6;
-                case GalTextureFormat.Astc2D10x8:  return 8;
-                case GalTextureFormat.Astc2D12x10: return 10;
-                case GalTextureFormat.Astc2D8x5:   return 5;
-                case GalTextureFormat.Astc2D10x5:  return 5;
-                case GalTextureFormat.Astc2D10x6:  return 6;
+                case GalImageFormat.ASTC_4x4_UNORM_BLOCK:   return 4;
+                case GalImageFormat.ASTC_5x5_UNORM_BLOCK:   return 5;
+                case GalImageFormat.ASTC_6x6_UNORM_BLOCK:   return 6;
+                case GalImageFormat.ASTC_8x8_UNORM_BLOCK:   return 8;
+                case GalImageFormat.ASTC_10x10_UNORM_BLOCK: return 10;
+                case GalImageFormat.ASTC_12x12_UNORM_BLOCK: return 12;
+                case GalImageFormat.ASTC_5x4_UNORM_BLOCK:   return 4;
+                case GalImageFormat.ASTC_6x5_UNORM_BLOCK:   return 5;
+                case GalImageFormat.ASTC_8x6_UNORM_BLOCK:   return 6;
+                case GalImageFormat.ASTC_10x8_UNORM_BLOCK:  return 8;
+                case GalImageFormat.ASTC_12x10_UNORM_BLOCK: return 10;
+                case GalImageFormat.ASTC_8x5_UNORM_BLOCK:   return 5;
+                case GalImageFormat.ASTC_10x5_UNORM_BLOCK:  return 5;
+                case GalImageFormat.ASTC_10x6_UNORM_BLOCK:  return 6;
             }
 
             throw new ArgumentException(nameof(Format));
         }
 
-        public bool TryGetCachedTexture(long Key, long DataSize, out GalTexture Texture)
+        public bool TryGetCachedTexture(long Key, long DataSize, out GalImage Image)
         {
             if (TextureCache.TryGetSize(Key, out long Size) && Size == DataSize)
             {
-                if (TextureCache.TryGetValue(Key, out TCE CachedTexture))
+                if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
                 {
-                    Texture = CachedTexture.Texture;
+                    Image = CachedImage.Image;
 
                     return true;
                 }
             }
 
-            Texture = default(GalTexture);
+            Image = default(GalImage);
 
             return false;
         }
 
         public void Bind(long Key, int Index)
         {
-            if (TextureCache.TryGetValue(Key, out TCE CachedTexture))
+            if (TextureCache.TryGetValue(Key, out ImageHandler CachedImage))
             {
                 GL.ActiveTexture(TextureUnit.Texture0 + Index);
 
-                GL.BindTexture(TextureTarget.Texture2D, CachedTexture.Handle);
+                GL.BindTexture(TextureTarget.Texture2D, CachedImage.Handle);
             }
         }
 
@@ -208,18 +217,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color);
         }
 
-        private static bool IsCompressedTextureFormat(GalTextureFormat Format)
+        private static bool IsCompressedTextureFormat(GalImageFormat Format)
         {
             switch (Format)
             {
-                case GalTextureFormat.BC6H_UF16:
-                case GalTextureFormat.BC6H_SF16:
-                case GalTextureFormat.BC7U:
-                case GalTextureFormat.BC1:
-                case GalTextureFormat.BC2:
-                case GalTextureFormat.BC3:
-                case GalTextureFormat.BC4:
-                case GalTextureFormat.BC5:
+                case GalImageFormat.BC6H_UFLOAT_BLOCK:
+                case GalImageFormat.BC6H_SFLOAT_BLOCK:
+                case GalImageFormat.BC7_UNORM_BLOCK:
+                case GalImageFormat.BC1_RGBA_UNORM_BLOCK:
+                case GalImageFormat.BC2_UNORM_BLOCK:
+                case GalImageFormat.BC3_UNORM_BLOCK:
+                case GalImageFormat.BC4_SNORM_BLOCK:
+                case GalImageFormat.BC4_UNORM_BLOCK:
+                case GalImageFormat.BC5_SNORM_BLOCK:
+                case GalImageFormat.BC5_UNORM_BLOCK:
                     return true;
             }
 
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
index ccc59e04..56745bc1 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -16,6 +16,7 @@ namespace Ryujinx.Graphics.Gal.Shader
         public const int VertexIdAttr    = 0x2fc;
         public const int FaceAttr        = 0x3fc;
 
+        public const int MaxFrameBufferAttachments = 8;
         public const int MaxUboSize = 1024;
 
         public const int GlPositionVec4Index = 7;
@@ -99,7 +100,11 @@ namespace Ryujinx.Graphics.Gal.Shader
 
             if (ShaderType == GalShaderType.Fragment)
             {
-                m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, false, 0, 4));
+                //Note: Replace 1 with MaxFrameBufferAttachments when attachments start to work
+                for (int Index = 0; Index < 1; Index++)
+                {
+                    m_Gprs.Add(Index * 4, new ShaderDeclInfo(FragmentOutputName + Index, Index * 4, false, 0, 4));
+                }
             }
 
             foreach (ShaderIrBlock Block in Blocks)
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
index 7f1cfabc..72637984 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -352,9 +352,9 @@ namespace Ryujinx.Graphics.Gal.Shader
                 {
                     Name = CustomType + " " + DeclInfo.Name + Suffix + ";";
                 }
-                else if (DeclInfo.Name == GlslDecl.FragmentOutputName)
+                else if (DeclInfo.Name.Contains(GlslDecl.FragmentOutputName))
                 {
-                    Name = "layout (location = 0) out vec4 " + DeclInfo.Name + Suffix + ";" + Environment.NewLine;
+                    Name = "layout (location = " + DeclInfo.Index / 4 + ") out vec4 " + DeclInfo.Name + Suffix + ";";
                 }
                 else
                 {
@@ -829,8 +829,11 @@ namespace Ryujinx.Graphics.Gal.Shader
                 {
                     return "gl_PointSize";
                 }
+            }
 
-                throw new InvalidOperationException();
+            if (DeclInfo.Index >= 16)
+            {
+                throw new InvalidOperationException($"Shader attribute offset {Abuf.Offs} is invalid.");
             }
 
             if (Decl.ShaderType == GalShaderType.Geometry)
@@ -876,7 +879,7 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetNameWithSwizzle(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, int Index)
         {
-            int VecIndex = Index >> 2;
+            int VecIndex = Index & ~3;
 
             if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo))
             {
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs
index d2c5f126..7fb5ea8a 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine2d.cs
@@ -154,16 +154,12 @@ namespace Ryujinx.HLE.Gpu.Engines
             }
             else if (IsDstFb)
             {
-                //Texture -> Frame Buffer copy.
-                const GalTextureFormat Format = GalTextureFormat.A8B8G8R8;
-
                 byte[] Buffer = TextureReader.Read(Vmm, SrcTexture());
 
                 Gpu.Renderer.FrameBuffer.SetBufferData(
                     DstKey,
                     DstWidth,
                     DstHeight,
-                    Format,
                     Buffer);
             }
             else
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
index 6f601244..1d0834dd 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
@@ -102,7 +102,9 @@ namespace Ryujinx.HLE.Gpu.Engines
             SetAlphaBlending(State);
             SetPrimitiveRestart(State);
 
+            //Enabling multiple framebuffer attachments cause graphics reggresions
             SetFrameBuffer(Vmm, 0);
+            SetZeta(Vmm);
 
             long[] Keys = UploadShaders(Vmm);
 
@@ -149,9 +151,11 @@ namespace Ryujinx.HLE.Gpu.Engines
             int Stencil = ReadRegister(NvGpuEngine3dReg.ClearStencil);
 
             SetFrameBuffer(Vmm, FbIndex);
+            SetZeta(Vmm);
 
             Gpu.Renderer.Rasterizer.ClearBuffers(
                 Flags,
+                FbIndex,
                 Red, Green, Blue, Alpha,
                 Depth,
                 Stencil);
@@ -161,6 +165,15 @@ namespace Ryujinx.HLE.Gpu.Engines
         {
             long VA = MakeInt64From2xInt32(NvGpuEngine3dReg.FrameBufferNAddress + FbIndex * 0x10);
 
+            int Format = ReadRegister(NvGpuEngine3dReg.FrameBufferNFormat + FbIndex * 0x10);
+
+            if (VA == 0 || Format == 0)
+            {
+                Gpu.Renderer.FrameBuffer.UnbindColor(FbIndex);
+
+                return;
+            }
+
             long Key = Vmm.GetPhysicalAddress(VA);
 
             FrameBuffers.Add(Key);
@@ -168,11 +181,11 @@ namespace Ryujinx.HLE.Gpu.Engines
             int Width  = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth  + FbIndex * 0x10);
             int Height = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10);
 
-            float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 4);
-            float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 4);
+            float TX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateX + FbIndex * 8);
+            float TY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNTranslateY + FbIndex * 8);
 
-            float SX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + FbIndex * 4);
-            float SY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + FbIndex * 4);
+            float SX = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleX + FbIndex * 8);
+            float SY = ReadRegisterFloat(NvGpuEngine3dReg.ViewportNScaleY + FbIndex * 8);
 
             int VpX = (int)MathF.Max(0, TX - MathF.Abs(SX));
             int VpY = (int)MathF.Max(0, TY - MathF.Abs(SY));
@@ -180,12 +193,48 @@ namespace Ryujinx.HLE.Gpu.Engines
             int VpW = (int)(TX + MathF.Abs(SX)) - VpX;
             int VpH = (int)(TY + MathF.Abs(SY)) - VpY;
 
-            Gpu.Renderer.FrameBuffer.Create(Key, Width, Height);
-            Gpu.Renderer.FrameBuffer.Bind(Key);
+            GalImageFormat ImageFormat = ImageFormatConverter.ConvertFrameBuffer((GalFrameBufferFormat)Format);
+
+            GalImage Image = new GalImage(Width, Height, ImageFormat);
+
+            long Size = TextureHelper.GetTextureSize(Image);
+
+            Gpu.Renderer.Texture.CreateFb(Key, Size, Image);
+            Gpu.Renderer.FrameBuffer.BindColor(Key, FbIndex);
 
             Gpu.Renderer.FrameBuffer.SetViewport(VpX, VpY, VpW, VpH);
         }
 
+        private void SetZeta(NvGpuVmm Vmm)
+        {
+            long ZA = MakeInt64From2xInt32(NvGpuEngine3dReg.ZetaAddress);
+
+            int Format = ReadRegister(NvGpuEngine3dReg.ZetaFormat);
+
+            bool ZetaEnable = (ReadRegister(NvGpuEngine3dReg.ZetaEnable) & 1) != 0;
+
+            if (ZA == 0 || Format == 0 || !ZetaEnable)
+            {
+                Gpu.Renderer.FrameBuffer.UnbindZeta();
+
+                return;
+            }
+
+            long Key = Vmm.GetPhysicalAddress(ZA);
+            
+            int Width  = ReadRegister(NvGpuEngine3dReg.ZetaHoriz);
+            int Height = ReadRegister(NvGpuEngine3dReg.ZetaVert);
+
+            GalImageFormat ImageFormat = ImageFormatConverter.ConvertZeta((GalZetaFormat)Format);
+
+            GalImage Image = new GalImage(Width, Height, ImageFormat);
+
+            long Size = TextureHelper.GetTextureSize(Image);
+
+            Gpu.Renderer.Texture.CreateFb(Key, Size, Image);
+            Gpu.Renderer.FrameBuffer.BindZeta(Key);
+        }
+
         private long[] UploadShaders(NvGpuVmm Vmm)
         {
             long[] Keys = new long[5];
@@ -442,15 +491,15 @@ namespace Ryujinx.HLE.Gpu.Engines
             }
             else
             {
-                GalTexture NewTexture = TextureFactory.MakeTexture(Vmm, TicPosition);
+                GalImage NewImage = TextureFactory.MakeTexture(Vmm, TicPosition);
 
-                long Size = (uint)TextureHelper.GetTextureSize(NewTexture);
+                long Size = (uint)TextureHelper.GetTextureSize(NewImage);
 
                 bool HasCachedTexture = false;
 
-                if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalTexture Texture))
+                if (Gpu.Renderer.Texture.TryGetCachedTexture(Key, Size, out GalImage Image))
                 {
-                    if (NewTexture.Equals(Texture) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture))
+                    if (NewImage.Equals(Image) && !QueryKeyUpload(Vmm, Key, Size, NvGpuBufferType.Texture))
                     {
                         Gpu.Renderer.Texture.Bind(Key, TexIndex);
 
@@ -462,7 +511,7 @@ namespace Ryujinx.HLE.Gpu.Engines
                 {
                     byte[] Data = TextureFactory.GetTextureData(Vmm, TicPosition);
 
-                    Gpu.Renderer.Texture.Create(Key, Data, NewTexture);
+                    Gpu.Renderer.Texture.Create(Key, Data, NewImage);
                 }
 
                 Gpu.Renderer.Texture.Bind(Key, TexIndex);
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
index 39a5ee8c..b03aef02 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
@@ -22,7 +22,14 @@ namespace Ryujinx.HLE.Gpu.Engines
         StencilBackFuncRef   = 0x3d5,
         StencilBackMask      = 0x3d6,
         StencilBackFuncMask  = 0x3d7,
+        ZetaAddress          = 0x3f8,
+        ZetaFormat           = 0x3fa,
+        ZetaBlockDimensions  = 0x3fb,
+        ZetaLayerStride      = 0x3fc,
         VertexAttribNFormat  = 0x458,
+        ZetaHoriz            = 0x48a,
+        ZetaVert             = 0x48b,
+        ZetaArrayMode        = 0x48c,
         DepthTestEnable      = 0x4b3,
         IBlendEnable         = 0x4b9,
         DepthTestFunction    = 0x4c3,
@@ -44,6 +51,7 @@ namespace Ryujinx.HLE.Gpu.Engines
         StencilFrontFuncMask = 0x4e6,
         StencilFrontMask     = 0x4e7,
         VertexArrayElemBase  = 0x50d,
+        ZetaEnable           = 0x54e,
         TexHeaderPoolOffset  = 0x55d,
         TexSamplerPoolOffset = 0x557,
         StencilTwoSideEnable = 0x565,
diff --git a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs
index 4db0b6f1..0ef33d3b 100644
--- a/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs
+++ b/Ryujinx.HLE/Gpu/Texture/TextureFactory.cs
@@ -6,11 +6,16 @@ namespace Ryujinx.HLE.Gpu.Texture
 {
     static class TextureFactory
     {
-        public static GalTexture MakeTexture(NvGpuVmm Vmm, long TicPosition)
+        public static GalImage MakeTexture(NvGpuVmm Vmm, long TicPosition)
         {
             int[] Tic = ReadWords(Vmm, TicPosition, 8);
 
-            GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
+            GalTextureType RType = (GalTextureType)((Tic[0] >> 7)  & 7);
+            GalTextureType GType = (GalTextureType)((Tic[0] >> 10) & 7);
+            GalTextureType BType = (GalTextureType)((Tic[0] >> 13) & 7);
+            GalTextureType AType = (GalTextureType)((Tic[0] >> 16) & 7);
+
+            GalImageFormat Format = ImageFormatConverter.ConvertTexture((GalTextureFormat)(Tic[0] & 0x7f), RType, GType, BType, AType);
 
             GalTextureSource XSource = (GalTextureSource)((Tic[0] >> 19) & 7);
             GalTextureSource YSource = (GalTextureSource)((Tic[0] >> 22) & 7);
@@ -20,7 +25,7 @@ namespace Ryujinx.HLE.Gpu.Texture
             int Width  = (Tic[4] & 0xffff) + 1;
             int Height = (Tic[5] & 0xffff) + 1;
 
-            return new GalTexture(
+            return new GalImage(
                 Width,
                 Height,
                 Format,
diff --git a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs
index 10a64f36..92b608a9 100644
--- a/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs
+++ b/Ryujinx.HLE/Gpu/Texture/TextureHelper.cs
@@ -30,117 +30,151 @@ namespace Ryujinx.HLE.Gpu.Texture
             throw new NotImplementedException(Texture.Swizzle.ToString());
         }
 
-        public static int GetTextureSize(GalTexture Texture)
+        public static int GetTextureSize(GalImage Image)
         {
-            switch (Texture.Format)
+            switch (Image.Format)
             {
-                case GalTextureFormat.R32G32B32A32:
-                    return Texture.Width * Texture.Height * 16;
+                case GalImageFormat.R32G32B32A32_SFLOAT:
+                case GalImageFormat.R32G32B32A32_SINT:
+                case GalImageFormat.R32G32B32A32_UINT:
+                    return Image.Width * Image.Height * 16;
 
-                case GalTextureFormat.R16G16B16A16:
-                    return Texture.Width * Texture.Height * 8;
+                case GalImageFormat.R16G16B16A16_SFLOAT:
+                case GalImageFormat.R16G16B16A16_SINT:
+                case GalImageFormat.R16G16B16A16_SNORM:
+                case GalImageFormat.R16G16B16A16_UINT:
+                case GalImageFormat.R16G16B16A16_UNORM:
+                    return Image.Width * Image.Height * 8;
 
-                case GalTextureFormat.A8B8G8R8:
-                case GalTextureFormat.A2B10G10R10:
-                case GalTextureFormat.R32:
-                case GalTextureFormat.ZF32:
-                case GalTextureFormat.BF10GF11RF11:
-                case GalTextureFormat.Z24S8:
-                    return Texture.Width * Texture.Height * 4;
+                case GalImageFormat.A8B8G8R8_SINT_PACK32:
+                case GalImageFormat.A8B8G8R8_SNORM_PACK32:
+                case GalImageFormat.A8B8G8R8_UINT_PACK32:
+                case GalImageFormat.A8B8G8R8_UNORM_PACK32:
+                case GalImageFormat.A8B8G8R8_SRGB_PACK32:
+                case GalImageFormat.A2B10G10R10_SINT_PACK32:
+                case GalImageFormat.A2B10G10R10_SNORM_PACK32:
+                case GalImageFormat.A2B10G10R10_UINT_PACK32:
+                case GalImageFormat.A2B10G10R10_UNORM_PACK32:
+                case GalImageFormat.R16G16_SFLOAT:
+                case GalImageFormat.R16G16_SINT:
+                case GalImageFormat.R16G16_SNORM:
+                case GalImageFormat.R16G16_UINT:
+                case GalImageFormat.R16G16_UNORM:
+                case GalImageFormat.R32_SFLOAT:
+                case GalImageFormat.R32_SINT:
+                case GalImageFormat.R32_UINT:
+                case GalImageFormat.D32_SFLOAT:
+                case GalImageFormat.B10G11R11_UFLOAT_PACK32:
+                case GalImageFormat.D24_UNORM_S8_UINT:
+                    return Image.Width * Image.Height * 4;
 
-                case GalTextureFormat.A1B5G5R5:
-                case GalTextureFormat.B5G6R5:
-                case GalTextureFormat.G8R8:
-                case GalTextureFormat.R16:
-                    return Texture.Width * Texture.Height * 2;
+                case GalImageFormat.B4G4R4A4_UNORM_PACK16:
+                case GalImageFormat.A1R5G5B5_UNORM_PACK16:
+                case GalImageFormat.B5G6R5_UNORM_PACK16:
+                case GalImageFormat.R8G8_SINT:
+                case GalImageFormat.R8G8_SNORM:
+                case GalImageFormat.R8G8_UINT:
+                case GalImageFormat.R8G8_UNORM:
+                case GalImageFormat.R16_SFLOAT:
+                case GalImageFormat.R16_SINT:
+                case GalImageFormat.R16_SNORM:
+                case GalImageFormat.R16_UINT:
+                case GalImageFormat.R16_UNORM:
+                case GalImageFormat.D16_UNORM:
+                    return Image.Width * Image.Height * 2;
 
-                case GalTextureFormat.R8:
-                    return Texture.Width * Texture.Height;
+                case GalImageFormat.R8_SINT:
+                case GalImageFormat.R8_SNORM:
+                case GalImageFormat.R8_UINT:
+                case GalImageFormat.R8_UNORM:
+                    return Image.Width * Image.Height;
 
-                case GalTextureFormat.BC1:
-                case GalTextureFormat.BC4:
+                case GalImageFormat.BC1_RGBA_UNORM_BLOCK:
+                case GalImageFormat.BC4_SNORM_BLOCK:
+                case GalImageFormat.BC4_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 4, 4, 8);
+                    return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 8);
                 }
 
-                case GalTextureFormat.BC6H_SF16:
-                case GalTextureFormat.BC6H_UF16:
-                case GalTextureFormat.BC7U:
-                case GalTextureFormat.BC2:
-                case GalTextureFormat.BC3:
-                case GalTextureFormat.BC5:
-                case GalTextureFormat.Astc2D4x4:
+                case GalImageFormat.BC6H_SFLOAT_BLOCK:
+                case GalImageFormat.BC6H_UFLOAT_BLOCK:
+                case GalImageFormat.BC7_UNORM_BLOCK:
+                case GalImageFormat.BC2_UNORM_BLOCK:
+                case GalImageFormat.BC3_UNORM_BLOCK:
+                case GalImageFormat.BC5_SNORM_BLOCK:
+                case GalImageFormat.BC5_UNORM_BLOCK:
+                case GalImageFormat.ASTC_4x4_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 4, 4, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 4, 4, 16);
                 }
 
-                case GalTextureFormat.Astc2D5x5:
+                case GalImageFormat.ASTC_5x5_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 5, 5, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 5, 5, 16);
                 }
 
-                case GalTextureFormat.Astc2D6x6:
+                case GalImageFormat.ASTC_6x6_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 6, 6, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 6, 6, 16);
                 }
 
-                case GalTextureFormat.Astc2D8x8:
+                case GalImageFormat.ASTC_8x8_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 8, 8, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 8, 8, 16);
                 }
 
-                case GalTextureFormat.Astc2D10x10:
+                case GalImageFormat.ASTC_10x10_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 10, 10, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 10, 10, 16);
                 }
 
-                case GalTextureFormat.Astc2D12x12:
+                case GalImageFormat.ASTC_12x12_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 12, 12, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 12, 12, 16);
                 }
 
-                case GalTextureFormat.Astc2D5x4:
+                case GalImageFormat.ASTC_5x4_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 5, 4, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 5, 4, 16);
                 }
 
-                case GalTextureFormat.Astc2D6x5:
+                case GalImageFormat.ASTC_6x5_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 6, 5, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 6, 5, 16);
                 }
 
-                case GalTextureFormat.Astc2D8x6:
+                case GalImageFormat.ASTC_8x6_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 8, 6, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 8, 6, 16);
                 }
 
-                case GalTextureFormat.Astc2D10x8:
+                case GalImageFormat.ASTC_10x8_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 10, 8, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 10, 8, 16);
                 }
 
-                case GalTextureFormat.Astc2D12x10:
+                case GalImageFormat.ASTC_12x10_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 12, 10, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 12, 10, 16);
                 }
 
-                case GalTextureFormat.Astc2D8x5:
+                case GalImageFormat.ASTC_8x5_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 8, 5, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 8, 5, 16);
                 }
 
-                case GalTextureFormat.Astc2D10x5:
+                case GalImageFormat.ASTC_10x5_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 10, 5, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 10, 5, 16);
                 }
 
-                case GalTextureFormat.Astc2D10x6:
+                case GalImageFormat.ASTC_10x6_UNORM_BLOCK:
                 {
-                    return CompressedTextureSize(Texture.Width, Texture.Height, 10, 6, 16);
+                    return CompressedTextureSize(Image.Width, Image.Height, 10, 6, 16);
                 }
             }
 
-            throw new NotImplementedException("0x" + Texture.Format.ToString("x2"));
+            throw new NotImplementedException("0x" + Image.Format.ToString("x2"));
         }
 
         public static int CompressedTextureSize(int TextureWidth, int TextureHeight, int BlockWidth, int BlockHeight, int Bpb)
diff --git a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs
index 0cf055db..19aa25d7 100644
--- a/Ryujinx.HLE/Gpu/Texture/TextureReader.cs
+++ b/Ryujinx.HLE/Gpu/Texture/TextureReader.cs
@@ -19,6 +19,7 @@ namespace Ryujinx.HLE.Gpu.Texture
                 case GalTextureFormat.Z24S8:        return Read4Bpp                  (Memory, Texture);
                 case GalTextureFormat.A1B5G5R5:     return Read5551                  (Memory, Texture);
                 case GalTextureFormat.B5G6R5:       return Read565                   (Memory, Texture);
+                case GalTextureFormat.A4B4G4R4:     return Read2Bpp                  (Memory, Texture);
                 case GalTextureFormat.G8R8:         return Read2Bpp                  (Memory, Texture);
                 case GalTextureFormat.R16:          return Read2Bpp                  (Memory, Texture);
                 case GalTextureFormat.R8:           return Read1Bpp                  (Memory, Texture);