From 624e813cd3e5d782847c577c2da0cfb8a121fd18 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 23 Aug 2018 02:07:23 -0300
Subject: [PATCH] Implement multiple rendertarget attachments and depth
 writting (#375)

* Add depth writting

* Implement multiple attachments

* Address feedback
---
 Ryujinx.Graphics/Gal/IGalFrameBuffer.cs       |  2 +
 Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs | 33 ++++----
 Ryujinx.Graphics/Gal/Shader/GlslDecl.cs       | 35 +++++++--
 Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs | 45 ++++++++++-
 Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs   | 75 ++++++++++++++++++-
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs      | 38 +++++++++-
 Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs   |  2 +
 7 files changed, 201 insertions(+), 29 deletions(-)

diff --git a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
index bce1981a47..108d3d9b1d 100644
--- a/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/IGalFrameBuffer.cs
@@ -18,6 +18,8 @@ namespace Ryujinx.Graphics.Gal
 
         void Set(byte[] Data, int Width, int Height);
 
+        void SetMap(int[] Map);
+
         void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom);
 
         void SetWindowSize(int Width, int Height);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
index e0f12e4eca..12239c4f06 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
@@ -21,18 +21,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
         }
 
-        private static readonly DrawBuffersEnum[] DrawBuffers = new DrawBuffersEnum[]
-        {
-            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;
 
@@ -194,6 +182,25 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             ReadTex = RawTex;
         }
 
+        public void SetMap(int[] Map)
+        {
+            if (Map != null && Map.Length > 0)
+            {
+                DrawBuffersEnum[] Mode = new DrawBuffersEnum[Map.Length];
+
+                for (int i = 0; i < Map.Length; i++)
+                {
+                    Mode[i] = DrawBuffersEnum.ColorAttachment0 + Map[i];
+                }
+
+                GL.DrawBuffers(Mode.Length, Mode);
+            }
+            else
+            {
+                GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+            }
+        }
+
         public void SetTransform(bool FlipX, bool FlipY, int Top, int Left, int Right, int Bottom)
         {
             this.FlipX = FlipX;
@@ -421,8 +428,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
 
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
-
-            GL.DrawBuffers(8, DrawBuffers);
         }
 
         private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment)
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
index 56745bc150..25f64db83d 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -16,7 +16,6 @@ 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;
@@ -94,16 +93,33 @@ namespace Ryujinx.Graphics.Gal.Shader
             m_Preds = new Dictionary<int, ShaderDeclInfo>();
         }
 
-        public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType) : this(ShaderType)
+        public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType, ShaderHeader Header)
+            : this(ShaderType)
         {
             StagePrefix = StagePrefixes[(int)ShaderType] + "_";
 
             if (ShaderType == GalShaderType.Fragment)
             {
-                //Note: Replace 1 with MaxFrameBufferAttachments when attachments start to work
-                for (int Index = 0; Index < 1; Index++)
+                int Index = 0;
+
+                for (int Attachment = 0; Attachment < 8; Attachment++)
                 {
-                    m_Gprs.Add(Index * 4, new ShaderDeclInfo(FragmentOutputName + Index, Index * 4, false, 0, 4));
+                    for (int Component = 0; Component < 4; Component++)
+                    {
+                        if (Header.OmapTargets[Attachment].ComponentEnabled(Component))
+                        {
+                            m_Gprs.TryAdd(Index, new ShaderDeclInfo(GetGprName(Index), Index));
+
+                            Index++;
+                        }
+                    }
+                }
+
+                if (Header.OmapDepth)
+                {
+                    Index = Header.DepthRegister;
+
+                    m_Gprs.TryAdd(Index, new ShaderDeclInfo(GetGprName(Index), Index));
                 }
             }
 
@@ -153,6 +169,11 @@ namespace Ryujinx.Graphics.Gal.Shader
             return Combined;
         }
 
+        public static string GetGprName(int Index)
+        {
+            return GprName + Index;
+        }
+
         private static void Merge(
             Dictionary<int, ShaderDeclInfo> C,
             Dictionary<int, ShaderDeclInfo> A,
@@ -316,9 +337,9 @@ namespace Ryujinx.Graphics.Gal.Shader
 
                 case ShaderIrOperGpr Gpr:
                 {
-                    if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index))
+                    if (!Gpr.IsConst)
                     {
-                        string Name = GprName + Gpr.Index;
+                        string Name = GetGprName(Gpr.Index);
 
                         m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index));
                     }
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
index 726379846d..8baf30e039 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -120,8 +120,8 @@ namespace Ryujinx.Graphics.Gal.Shader
             Blocks  = ShaderDecoder.Decode(Memory, VpAPosition);
             BlocksB = ShaderDecoder.Decode(Memory, VpBPosition);
 
-            GlslDecl DeclVpA = new GlslDecl(Blocks,  ShaderType);
-            GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType);
+            GlslDecl DeclVpA = new GlslDecl(Blocks,  ShaderType, Header);
+            GlslDecl DeclVpB = new GlslDecl(BlocksB, ShaderType, HeaderB);
 
             Decl = GlslDecl.Merge(DeclVpA, DeclVpB);
 
@@ -136,7 +136,7 @@ namespace Ryujinx.Graphics.Gal.Shader
             Blocks  = ShaderDecoder.Decode(Memory, Position);
             BlocksB = null;
 
-            Decl = new GlslDecl(Blocks, ShaderType);
+            Decl = new GlslDecl(Blocks, ShaderType, Header);
 
             return Decompile();
         }
@@ -304,7 +304,17 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private void PrintDeclOutAttributes()
         {
-            if (Decl.ShaderType != GalShaderType.Fragment)
+            if (Decl.ShaderType == GalShaderType.Fragment)
+            {
+                for (int Attachment = 0; Attachment < 8; Attachment++)
+                {
+                    if (Header.OmapTargets[Attachment].Enabled)
+                    {
+                        SB.AppendLine("layout (location = " + Attachment + ") out vec4 " + GlslDecl.FragmentOutputName + Attachment + ";");
+                    }
+                }
+            }
+            else
             {
                 SB.AppendLine("layout (location = " + GlslDecl.PositionOutAttrLocation + ") out vec4 " + GlslDecl.PositionOutAttrName + ";");
             }
@@ -432,6 +442,33 @@ namespace Ryujinx.Graphics.Gal.Shader
                 PrintAttrToOutput();
             }
 
+            if (Decl.ShaderType == GalShaderType.Fragment)
+            {
+                if (Header.OmapDepth)
+                {
+                    SB.AppendLine(IdentationStr + "gl_FragDepth = " + GlslDecl.GetGprName(Header.DepthRegister) + ";");
+                }
+
+                int GprIndex = 0;
+
+                for (int Attachment = 0; Attachment < 8; Attachment++)
+                {
+                    string Output = GlslDecl.FragmentOutputName + Attachment;
+
+                    OmapTarget Target = Header.OmapTargets[Attachment];
+
+                    for (int Component = 0; Component < 4; Component++)
+                    {
+                        if (Target.ComponentEnabled(Component))
+                        {
+                            SB.AppendLine(IdentationStr + Output + "[" + Component + "] = " + GlslDecl.GetGprName(GprIndex) + ";");
+
+                            GprIndex++;
+                        }
+                    }
+                }
+            }
+
             SB.AppendLine("}");
         }
 
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs
index 8e5057ed9e..eca90fc3aa 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderHeader.cs
@@ -1,5 +1,30 @@
-namespace Ryujinx.Graphics.Gal.Shader
+using System;
+
+namespace Ryujinx.Graphics.Gal.Shader
 {
+    struct OmapTarget
+    {
+        public bool Red;
+        public bool Green;
+        public bool Blue;
+        public bool Alpha;
+
+        public bool Enabled => Red || Green || Blue || Alpha;
+
+        public bool ComponentEnabled(int Component)
+        {
+            switch (Component)
+            {
+                case 0: return Red;
+                case 1: return Green;
+                case 2: return Blue;
+                case 3: return Alpha;
+            }
+
+            throw new ArgumentException(nameof(Component));
+        }
+    }
+
     class ShaderHeader
     {
         public const int PointList     = 1;
@@ -30,6 +55,10 @@
         public int StoreReqStart        { get; private set; }
         public int StoreReqEnd          { get; private set; }
 
+        public OmapTarget[] OmapTargets    { get; private set; }
+        public bool         OmapSampleMask { get; private set; }
+        public bool         OmapDepth      { get; private set; }
+
         public ShaderHeader(IGalMemory Memory, long Position)
         {
             uint CommonWord0 = (uint)Memory.ReadInt32(Position + 0);
@@ -61,6 +90,50 @@
             MaxOutputVertexCount = ReadBits(CommonWord4,  0, 12);
             StoreReqStart        = ReadBits(CommonWord4, 12,  8);
             StoreReqEnd          = ReadBits(CommonWord4, 24,  8);
+
+            //Type 2 (fragment?) reading
+            uint Type2OmapTarget = (uint)Memory.ReadInt32(Position + 72);
+            uint Type2Omap       = (uint)Memory.ReadInt32(Position + 76);
+
+            OmapTargets = new OmapTarget[8];
+
+            for (int i = 0; i < OmapTargets.Length; i++)
+            {
+                int Offset = i * 4;
+
+                OmapTargets[i] = new OmapTarget
+                {
+                    Red   = ReadBits(Type2OmapTarget, Offset + 0, 1) != 0,
+                    Green = ReadBits(Type2OmapTarget, Offset + 1, 1) != 0,
+                    Blue  = ReadBits(Type2OmapTarget, Offset + 2, 1) != 0,
+                    Alpha = ReadBits(Type2OmapTarget, Offset + 3, 1) != 0
+                };
+            }
+
+            OmapSampleMask = ReadBits(Type2Omap, 0, 1) != 0;
+            OmapDepth      = ReadBits(Type2Omap, 1, 1) != 0;
+        }
+
+        public int DepthRegister
+        {
+            get
+            {
+                int Count = 0;
+
+                for (int Index = 0; Index < OmapTargets.Length; Index++)
+                {
+                    for (int Component = 0; Component < 4; Component++)
+                    {
+                        if (OmapTargets[Index].ComponentEnabled(Component))
+                        {
+                            Count++;
+                        }
+                    }
+                }
+
+                // Depth register is always two registers after the last color output
+                return Count + 1;
+            }
         }
 
         private static int ReadBits(uint Word, int Offset, int BitWidth)
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
index 419f6901e4..be47b66ca0 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3d.cs
@@ -102,10 +102,15 @@ namespace Ryujinx.HLE.Gpu.Engines
             SetAlphaBlending(State);
             SetPrimitiveRestart(State);
 
-            //Enabling multiple framebuffer attachments cause graphics reggresions
-            SetFrameBuffer(Vmm, 0);
+            for (int FbIndex = 0; FbIndex < 8; FbIndex++)
+            {
+                SetFrameBuffer(Vmm, 0);
+            }
+            
             SetZeta(Vmm);
 
+            SetRenderTargets();
+
             long[] Keys = UploadShaders(Vmm);
 
             Gpu.Renderer.Shader.BindProgram();
@@ -415,6 +420,33 @@ namespace Ryujinx.HLE.Gpu.Engines
             }
         }
 
+        private void SetRenderTargets()
+        {
+            bool SeparateFragData = (ReadRegister(NvGpuEngine3dReg.RTSeparateFragData) & 1) != 0;
+
+            if (SeparateFragData)
+            {
+                uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl));
+
+                uint Count = Control & 0xf;
+
+                int[] Map = new int[Count];
+
+                for (int i = 0; i < Count; i++)
+                {
+                    int Shift = 4 + i * 3;
+
+                    Map[i] = (int)((Control >> Shift) & 7);
+                }
+
+                Gpu.Renderer.FrameBuffer.SetMap(Map);
+            }
+            else
+            {
+                Gpu.Renderer.FrameBuffer.SetMap(null);
+            }
+        }
+
         private void UploadTextures(NvGpuVmm Vmm, GalPipelineState State, long[] Keys)
         {
             long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
@@ -801,4 +833,4 @@ namespace Ryujinx.HLE.Gpu.Engines
             return Vmm.IsRegionModified(Key, Size, Type);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
index b03aef0241..7eff13b58f 100644
--- a/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
+++ b/Ryujinx.HLE/Gpu/Engines/NvGpuEngine3dReg.cs
@@ -22,11 +22,13 @@ namespace Ryujinx.HLE.Gpu.Engines
         StencilBackFuncRef   = 0x3d5,
         StencilBackMask      = 0x3d6,
         StencilBackFuncMask  = 0x3d7,
+        RTSeparateFragData   = 0x3eb,
         ZetaAddress          = 0x3f8,
         ZetaFormat           = 0x3fa,
         ZetaBlockDimensions  = 0x3fb,
         ZetaLayerStride      = 0x3fc,
         VertexAttribNFormat  = 0x458,
+        RTControl            = 0x487,
         ZetaHoriz            = 0x48a,
         ZetaVert             = 0x48b,
         ZetaArrayMode        = 0x48c,