From 2562ca6c3fe6ef328e0926c9cbcd6bb52abb328f Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Tue, 25 Sep 2018 19:55:30 -0300
Subject: [PATCH] Fix multiple rendertargets (#427)

* Simplify render target bindings

* Implement multiple viewports

* Pack glViewportIndexed calls into a single glViewportArray

* Use ARB_viewport_array when available

* Cache framebuffer attachments

* Use get accessors in OGLExtension

* Address feedback
---
 Ryujinx.Graphics/Gal/IGalRenderTarget.cs      |  10 +-
 .../Gal/OpenGL/OGLEnumConverter.cs            |   2 +-
 Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs   |  37 +--
 .../Gal/OpenGL/OGLRenderTarget.cs             | 257 ++++++++++--------
 Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs      |   2 +-
 Ryujinx.Graphics/GpuResourceManager.cs        |   4 +-
 Ryujinx.Graphics/NvGpuEngine3d.cs             |  26 +-
 Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs      |   2 +-
 8 files changed, 185 insertions(+), 155 deletions(-)

diff --git a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs
index 7ccf0981d1..f941ccd584 100644
--- a/Ryujinx.Graphics/Gal/IGalRenderTarget.cs
+++ b/Ryujinx.Graphics/Gal/IGalRenderTarget.cs
@@ -2,15 +2,17 @@ namespace Ryujinx.Graphics.Gal
 {
     public interface IGalRenderTarget
     {
-        void BindColor(long Key, int Attachment, GalImage Image);
+        void Bind();
+
+        void BindColor(long Key, int Attachment);
 
         void UnbindColor(int Attachment);
 
-        void BindZeta(long Key, GalImage Image);
+        void BindZeta(long Key);
 
         void UnbindZeta();
 
-        void Set(long Key);
+        void Present(long Key);
 
         void SetMap(int[] Map);
 
@@ -18,7 +20,7 @@ namespace Ryujinx.Graphics.Gal
 
         void SetWindowSize(int Width, int Height);
 
-        void SetViewport(int X, int Y, int Width, int Height);
+        void SetViewport(int Attachment, int X, int Y, int Width, int Height);
 
         void Render();
 
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
index 187d1eece7..7f9e9fbe3d 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
@@ -225,7 +225,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 case GalTextureWrap.Clamp:          return TextureWrapMode.Clamp;
             }
 
-            if (OGLExtension.HasTextureMirrorClamp())
+            if (OGLExtension.TextureMirrorClamp)
             {
                 switch (Wrap)
                 {
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs
index 5ad422980c..11daeb593c 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLExtension.cs
@@ -1,40 +1,17 @@
 using OpenTK.Graphics.OpenGL;
+using System;
 
 namespace Ryujinx.Graphics.Gal.OpenGL
 {
     static class OGLExtension
     {
-        private static bool Initialized = false;
+        private static Lazy<bool> s_EnhancedLayouts    = new Lazy<bool>(() => HasExtension("GL_ARB_enhanced_layouts"));
+        private static Lazy<bool> s_TextureMirrorClamp = new Lazy<bool>(() => HasExtension("GL_EXT_texture_mirror_clamp"));
+        private static Lazy<bool> s_ViewportArray      = new Lazy<bool>(() => HasExtension("GL_ARB_viewport_array"));
 
-        private static bool EnhancedLayouts;
-
-        private static bool TextureMirrorClamp;
-
-        public static bool HasEnhancedLayouts()
-        {
-            EnsureInitialized();
-
-            return EnhancedLayouts;
-        }
-
-        public static bool HasTextureMirrorClamp()
-        {
-            EnsureInitialized();
-
-            return TextureMirrorClamp;
-        }
-
-        private static void EnsureInitialized()
-        {
-            if (Initialized)
-            {
-                return;
-            }
-
-            EnhancedLayouts = HasExtension("GL_ARB_enhanced_layouts");
-
-            TextureMirrorClamp = HasExtension("GL_EXT_texture_mirror_clamp");
-        }
+        public static bool EnhancedLayouts    => s_EnhancedLayouts.Value;
+        public static bool TextureMirrorClamp => s_TextureMirrorClamp.Value;
+        public static bool ViewportArray      => s_ViewportArray.Value;
 
         private static bool HasExtension(string Name)
         {
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs
index ff5dc1b895..78cf5d2fa9 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRenderTarget.cs
@@ -22,16 +22,52 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
         }
 
+        private class FrameBufferAttachments
+        {
+            public long[] Colors;
+            public long Zeta;
+
+            public int MapCount;
+            public DrawBuffersEnum[] Map;
+
+            public FrameBufferAttachments()
+            {
+                Colors = new long[RenderTargetsCount];
+
+                Map = new DrawBuffersEnum[RenderTargetsCount];
+            }
+
+            public void SetAndClear(FrameBufferAttachments Source)
+            {
+                Zeta     = Source.Zeta;
+                MapCount = Source.MapCount;
+
+                Source.Zeta     = 0;
+                Source.MapCount = 0;
+
+                for (int i = 0; i < RenderTargetsCount; i++)
+                {
+                    Colors[i] = Source.Colors[i];
+                    Map[i]    = Source.Map[i];
+
+                    Source.Colors[i] = 0;
+                    Source.Map[i]    = 0;
+                }
+            }
+        }
+
         private const int NativeWidth  = 1280;
         private const int NativeHeight = 720;
 
+        private const int RenderTargetsCount = 8;
+
         private const GalImageFormat RawFormat = GalImageFormat.A8B8G8R8 | GalImageFormat.Unorm;
 
         private OGLTexture Texture;
 
         private ImageHandler ReadTex;
 
-        private Rect Viewport;
+        private float[] Viewports;
         private Rect Window;
 
         private bool FlipX;
@@ -50,138 +86,164 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         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;
+        private FrameBufferAttachments Attachments;
+        private FrameBufferAttachments OldAttachments;
 
         private int CopyPBO;
 
         public OGLRenderTarget(OGLTexture Texture)
         {
-            ColorAttachments = new int[8];
+            Attachments = new FrameBufferAttachments();
+
+            OldAttachments = new FrameBufferAttachments();
+
+            Viewports = new float[RenderTargetsCount * 4];
 
             this.Texture = Texture;
         }
 
-        public void BindColor(long Key, int Attachment, GalImage Image)
+        public void Bind()
         {
-            if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage))
+            if (DummyFrameBuffer == 0)
             {
-                EnsureFrameBuffer();
-
-                Attach(ref ColorAttachments[Attachment], CachedImage.Handle, FramebufferAttachment.ColorAttachment0 + Attachment);
+                DummyFrameBuffer = GL.GenFramebuffer();
             }
-            else
+
+            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
+
+            ImageHandler CachedImage;
+
+            for (int Attachment = 0; Attachment < RenderTargetsCount; Attachment++)
             {
-                UnbindColor(Attachment);
-            }
-        }
-
-        public void UnbindColor(int Attachment)
-        {
-            EnsureFrameBuffer();
-
-            Attach(ref ColorAttachments[Attachment], 0, FramebufferAttachment.ColorAttachment0 + Attachment);
-        }
-
-        public void BindZeta(long Key, GalImage Image)
-        {
-            if (Texture.TryGetImageHandler(Key, out ImageHandler CachedImage))
-            {
-                EnsureFrameBuffer();
-
-                if (CachedImage.HasDepth && CachedImage.HasStencil)
+                if (Attachments.Colors[Attachment] == OldAttachments.Colors[Attachment])
                 {
-                    if (DepthAttachment   != CachedImage.Handle ||
-                        StencilAttachment != CachedImage.Handle)
+                    continue;
+                }
+
+                int Handle = 0;
+
+                if (Attachments.Colors[Attachment] != 0 &&
+                    Texture.TryGetImageHandler(Attachments.Colors[Attachment], out CachedImage))
+                {
+                    Handle = CachedImage.Handle;
+                }
+
+                GL.FramebufferTexture(
+                    FramebufferTarget.DrawFramebuffer,
+                    FramebufferAttachment.ColorAttachment0 + Attachment,
+                    Handle,
+                    0);
+            }
+
+            if (Attachments.Zeta != OldAttachments.Zeta)
+            {
+                if (Attachments.Zeta != 0 && Texture.TryGetImageHandler(Attachments.Zeta, out CachedImage))
+                {
+                    if (CachedImage.HasDepth && CachedImage.HasStencil)
                     {
                         GL.FramebufferTexture(
                             FramebufferTarget.DrawFramebuffer,
                             FramebufferAttachment.DepthStencilAttachment,
                             CachedImage.Handle,
                             0);
-
-                        DepthAttachment   = CachedImage.Handle;
-                        StencilAttachment = CachedImage.Handle;
                     }
-                }
-                else if (CachedImage.HasDepth)
-                {
-                    Attach(ref DepthAttachment, CachedImage.Handle, FramebufferAttachment.DepthAttachment);
+                    else if (CachedImage.HasDepth)
+                    {
+                        GL.FramebufferTexture(
+                            FramebufferTarget.DrawFramebuffer,
+                            FramebufferAttachment.DepthAttachment,
+                            CachedImage.Handle,
+                            0);
 
-                    Attach(ref StencilAttachment, 0, FramebufferAttachment.StencilAttachment);
-                }
-                else if (CachedImage.HasStencil)
-                {
-                    Attach(ref DepthAttachment, 0, FramebufferAttachment.DepthAttachment);
-
-                    Attach(ref StencilAttachment, CachedImage.Handle, FramebufferAttachment.StencilAttachment);
+                        GL.FramebufferTexture(
+                            FramebufferTarget.DrawFramebuffer,
+                            FramebufferAttachment.StencilAttachment,
+                            0,
+                            0);
+                    }
+                    else
+                    {
+                        throw new NotImplementedException();
+                    }
                 }
                 else
                 {
-                    throw new InvalidOperationException();
+                    GL.FramebufferTexture(
+                        FramebufferTarget.DrawFramebuffer,
+                        FramebufferAttachment.DepthStencilAttachment,
+                        0,
+                        0);
                 }
             }
+
+            if (OGLExtension.ViewportArray)
+            {
+                GL.ViewportArray(0, 8, Viewports);
+            }
             else
             {
-                UnbindZeta();
+                GL.Viewport(
+                    (int)Viewports[0],
+                    (int)Viewports[1],
+                    (int)Viewports[2],
+                    (int)Viewports[3]);
             }
-        }
 
-        private void Attach(ref int OldHandle, int NewHandle, FramebufferAttachment FbAttachment)
-        {
-            if (OldHandle != NewHandle)
+            if (Attachments.MapCount > 1)
             {
-                GL.FramebufferTexture(
-                    FramebufferTarget.DrawFramebuffer,
-                    FbAttachment,
-                    NewHandle,
-                    0);
-
-                OldHandle = NewHandle;
+                GL.DrawBuffers(Attachments.MapCount, Attachments.Map);
             }
+            else if (Attachments.MapCount == 1)
+            {
+                GL.DrawBuffer((DrawBufferMode)Attachments.Map[0]);
+            }
+            else
+            {
+                GL.DrawBuffer(DrawBufferMode.None);
+            }
+
+            OldAttachments.SetAndClear(Attachments);
         }
 
+        public void BindColor(long Key, int Attachment)
+        {
+            Attachments.Colors[Attachment] = Key;
+        }
+
+        public void UnbindColor(int Attachment)
+        {
+            Attachments.Colors[Attachment] = 0;
+        }
+
+        public void BindZeta(long Key)
+        {
+            Attachments.Zeta = Key;
+        }
+        
         public void UnbindZeta()
         {
-            EnsureFrameBuffer();
-
-            if (DepthAttachment != 0 || StencilAttachment != 0)
-            {
-                GL.FramebufferTexture(
-                    FramebufferTarget.DrawFramebuffer,
-                    FramebufferAttachment.DepthStencilAttachment,
-                    0,
-                    0);
-
-                DepthAttachment   = 0;
-                StencilAttachment = 0;
-            }
+            Attachments.Zeta = 0;
         }
 
-        public void Set(long Key)
+        public void Present(long Key)
         {
             Texture.TryGetImageHandler(Key, out ReadTex);
         }
 
         public void SetMap(int[] Map)
         {
-            if (Map != null && Map.Length > 0)
+            if (Map != null)
             {
-                DrawBuffersEnum[] Mode = new DrawBuffersEnum[Map.Length];
+                Attachments.MapCount = Map.Length;
 
-                for (int i = 0; i < Map.Length; i++)
+                for (int Attachment = 0; Attachment < Attachments.MapCount; Attachment++)
                 {
-                    Mode[i] = DrawBuffersEnum.ColorAttachment0 + Map[i];
+                    Attachments.Map[Attachment] = DrawBuffersEnum.ColorAttachment0 + Map[Attachment];
                 }
-
-                GL.DrawBuffers(Mode.Length, Mode);
             }
             else
             {
-                GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+                Attachments.MapCount = 0;
             }
         }
 
@@ -201,20 +263,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             Window = new Rect(0, 0, Width, Height);
         }
 
-        public void SetViewport(int X, int Y, int Width, int Height)
+        public void SetViewport(int Attachment, int X, int Y, int Width, int Height)
         {
-            Viewport = new Rect(X, Y, Width, Height);
+            int Offset = Attachment * 4;
 
-            SetViewport(Viewport);
-        }
-
-        private void SetViewport(Rect Viewport)
-        {
-            GL.Viewport(
-                Viewport.X,
-                Viewport.Y,
-                Viewport.Width,
-                Viewport.Height);
+            Viewports[Offset + 0] = X;
+            Viewports[Offset + 1] = Y;
+            Viewports[Offset + 2] = Width;
+            Viewports[Offset + 3] = Height;
         }
 
         public void Render()
@@ -276,7 +332,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.FramebufferTexture(FramebufferTarget.ReadFramebuffer, FramebufferAttachment.ColorAttachment0, ReadTex.Handle, 0);
 
             GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
-            GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
 
             GL.Clear(ClearBufferMask.ColorBufferBit);
 
@@ -285,8 +340,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 DstX0, DstY0, DstX1, DstY1,
                 ClearBufferMask.ColorBufferBit,
                 BlitFramebufferFilter.Linear);
-
-            EnsureFrameBuffer();
         }
 
         public void Copy(
@@ -343,8 +396,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 GL.Clear(Mask);
 
                 GL.BlitFramebuffer(SrcX0, SrcY0, SrcX1, SrcY1, DstX0, DstY0, DstX1, DstY1, Mask, Filter);
-
-                EnsureFrameBuffer();
             }
         }
 
@@ -419,15 +470,5 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                    (CachedImage.HasDepth   ? ClearBufferMask.DepthBufferBit   : 0) |
                    (CachedImage.HasStencil ? ClearBufferMask.StencilBufferBit : 0);
         }
-
-        private void EnsureFrameBuffer()
-        {
-            if (DummyFrameBuffer == 0)
-            {
-                DummyFrameBuffer = GL.GenFramebuffer();
-            }
-
-            GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, DummyFrameBuffer);
-        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
index 73d37b8791..efcb7e3471 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
@@ -133,7 +133,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             {
                 //Enhanced layouts are required for Geometry shaders
                 //skip this stage if current driver has no ARB_enhanced_layouts
-                if (!OGLExtension.HasEnhancedLayouts())
+                if (!OGLExtension.EnhancedLayouts)
                 {
                     return;
                 }
diff --git a/Ryujinx.Graphics/GpuResourceManager.cs b/Ryujinx.Graphics/GpuResourceManager.cs
index 0a8d201452..71390a83ac 100644
--- a/Ryujinx.Graphics/GpuResourceManager.cs
+++ b/Ryujinx.Graphics/GpuResourceManager.cs
@@ -46,7 +46,7 @@ namespace Ryujinx.Graphics
                 Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage);
             }
 
-            Gpu.Renderer.RenderTarget.BindColor(Position, Attachment, NewImage);
+            Gpu.Renderer.RenderTarget.BindColor(Position, Attachment);
         }
 
         public void SendZetaBuffer(NvGpuVmm Vmm, long Position, GalImage NewImage)
@@ -60,7 +60,7 @@ namespace Ryujinx.Graphics
                 Gpu.Renderer.Texture.Create(Position, (int)Size, NewImage);
             }
 
-            Gpu.Renderer.RenderTarget.BindZeta(Position, NewImage);
+            Gpu.Renderer.RenderTarget.BindZeta(Position);
         }
 
         public void SendTexture(NvGpuVmm Vmm, long Position, GalImage NewImage, int TexIndex = -1)
diff --git a/Ryujinx.Graphics/NvGpuEngine3d.cs b/Ryujinx.Graphics/NvGpuEngine3d.cs
index b19f3063bf..22c0937770 100644
--- a/Ryujinx.Graphics/NvGpuEngine3d.cs
+++ b/Ryujinx.Graphics/NvGpuEngine3d.cs
@@ -100,7 +100,10 @@ namespace Ryujinx.Graphics
             SetAlphaBlending(State);
             SetPrimitiveRestart(State);
 
-            SetFrameBuffer(Vmm, 0);
+            for (int FbIndex = 0; FbIndex < 8; FbIndex++)
+            {
+                SetFrameBuffer(Vmm, FbIndex);
+            }
 
             SetZeta(Vmm);
 
@@ -154,6 +157,10 @@ namespace Ryujinx.Graphics
 
             SetZeta(Vmm);
 
+            SetRenderTargets();
+
+            Gpu.Renderer.RenderTarget.Bind();
+
             Gpu.Renderer.Rasterizer.ClearBuffers(
                 Flags,
                 FbIndex,
@@ -204,7 +211,7 @@ namespace Ryujinx.Graphics
 
             Gpu.ResourceManager.SendColorBuffer(Vmm, Key, FbIndex, Image);
 
-            Gpu.Renderer.RenderTarget.SetViewport(VpX, VpY, VpW, VpH);
+            Gpu.Renderer.RenderTarget.SetViewport(FbIndex, VpX, VpY, VpW, VpH);
         }
 
         private void SetFrameBuffer(GalPipelineState State)
@@ -426,14 +433,15 @@ namespace Ryujinx.Graphics
 
         private void SetRenderTargets()
         {
-            bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData);
+            //Commercial games do not seem to 
+            //bool SeparateFragData = ReadRegisterBool(NvGpuEngine3dReg.RTSeparateFragData);
 
-            if (SeparateFragData)
+            uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl));
+
+            uint Count = Control & 0xf;
+
+            if (Count > 0)
             {
-                uint Control = (uint)(ReadRegister(NvGpuEngine3dReg.RTControl));
-
-                uint Count = Control & 0xf;
-
                 int[] Map = new int[Count];
 
                 for (int i = 0; i < Count; i++)
@@ -702,6 +710,8 @@ namespace Ryujinx.Graphics
 
             Gpu.Renderer.Pipeline.Bind(State);
 
+            Gpu.Renderer.RenderTarget.Bind();
+
             if (IndexCount != 0)
             {
                 int IndexEntryFmt = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat);
diff --git a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs
index c5f38211b9..191537b1da 100644
--- a/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs
+++ b/Ryujinx.HLE/HOS/Services/Vi/NvFlinger.cs
@@ -328,7 +328,7 @@ namespace Ryujinx.HLE.HOS.Services.Android
                 Context.Device.Gpu.ResourceManager.SendTexture(Vmm, FbAddr, Image);
 
                 Renderer.RenderTarget.SetTransform(FlipX, FlipY, Top, Left, Right, Bottom);
-                Renderer.RenderTarget.Set(FbAddr);
+                Renderer.RenderTarget.Present(FbAddr);
 
                 ReleaseBuffer(Slot);
             });