From ab4867505ec0d3f5a9ec4f042ccd6f7890e3632e Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Sun, 29 Mar 2020 00:02:58 -0300
Subject: [PATCH] Implement GPU scissors (#1058)

* Implement GPU scissors

* Remove unused using

* Add missing changes for Clear
---
 Ryujinx.Graphics.GAL/IPipeline.cs           |  5 +--
 Ryujinx.Graphics.Gpu/Engine/MethodClear.cs  |  6 ++++
 Ryujinx.Graphics.Gpu/Engine/Methods.cs      | 26 ++++++++++++++++
 Ryujinx.Graphics.Gpu/State/GpuStateTable.cs |  1 +
 Ryujinx.Graphics.Gpu/State/MethodOffset.cs  |  1 +
 Ryujinx.Graphics.Gpu/State/ScissorState.cs  | 12 ++++++++
 Ryujinx.Graphics.OpenGL/Pipeline.cs         | 34 +++++++++++++++++++++
 Ryujinx.Graphics.OpenGL/Renderer.cs         |  6 ++--
 Ryujinx.Graphics.OpenGL/TextureCopy.cs      | 11 +++++++
 Ryujinx.Graphics.OpenGL/Window.cs           | 12 ++++++--
 10 files changed, 107 insertions(+), 7 deletions(-)
 create mode 100644 Ryujinx.Graphics.Gpu/State/ScissorState.cs

diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index 1a5f1cf02f..fc5cf483db 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -29,9 +29,7 @@ namespace Ryujinx.Graphics.GAL
         void SetBlendColor(ColorF color);
 
         void SetDepthBias(PolygonModeMask enables, float factor, float units, float clamp);
-
         void SetDepthMode(DepthMode mode);
-
         void SetDepthTest(DepthTestDescriptor depthTest);
 
         void SetFaceCulling(bool enable, Face face);
@@ -56,6 +54,9 @@ namespace Ryujinx.Graphics.GAL
 
         void SetSampler(int index, ShaderStage stage, ISampler sampler);
 
+        void SetScissorEnable(int index, bool enable);
+        void SetScissor(int index, int x, int y, int width, int height);
+
         void SetStencilTest(StencilTestDescriptor stencilTest);
 
         void SetStorageBuffer(int index, ShaderStage stage, BufferRange buffer);
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
index dfa7b12ed3..ff538df332 100644
--- a/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodClear.cs
@@ -13,6 +13,12 @@ namespace Ryujinx.Graphics.Gpu.Engine
         /// <param name="argument">Method call argument</param>
         private void Clear(GpuState state, int argument)
         {
+            // Scissor affects clears aswell.
+            if (state.QueryModified(MethodOffset.ScissorState))
+            {
+                UpdateScissorState(state);
+            }
+
             UpdateRenderTargetState(state, useControl: false);
 
             TextureManager.CommitGraphicsBindings();
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index a2c9876e42..0b30e61d82 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -117,6 +117,11 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 UpdateRenderTargetState(state, useControl: true);
             }
 
+            if (state.QueryModified(MethodOffset.ScissorState))
+            {
+                UpdateScissorState(state);
+            }
+
             if (state.QueryModified(MethodOffset.DepthTestEnable,
                                     MethodOffset.DepthWriteEnable,
                                     MethodOffset.DepthTestFunc))
@@ -321,6 +326,27 @@ namespace Ryujinx.Graphics.Gpu.Engine
             return colorState.Format != 0 && colorState.WidthOrStride != 0;
         }
 
+        /// <summary>
+        /// Updates host scissor test state based on current GPU state.
+        /// </summary>
+        /// <param name="state">Current GPU state</param>
+        private void UpdateScissorState(GpuState state)
+        {
+            for (int index = 0; index < Constants.TotalViewports; index++)
+            {
+                ScissorState scissor = state.Get<ScissorState>(MethodOffset.ScissorState, index);
+
+                bool enable = scissor.Enable && (scissor.X1 != 0 || scissor.Y1 != 0 || scissor.X2 != 0xffff || scissor.Y2 != 0xffff);
+
+                _context.Renderer.Pipeline.SetScissorEnable(index, enable);
+
+                if (enable)
+                {
+                    _context.Renderer.Pipeline.SetScissor(index, scissor.X1, scissor.Y1, scissor.X2 - scissor.X1, scissor.Y2 - scissor.Y1);
+                }
+            }
+        }
+
         /// <summary>
         /// Updates host depth test state based on current GPU state.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs
index db8d141e77..8631efcc1f 100644
--- a/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs
+++ b/Ryujinx.Graphics.Gpu/State/GpuStateTable.cs
@@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.Gpu.State
             new TableItem(MethodOffset.ViewportExtents,        typeof(ViewportExtents),       8),
             new TableItem(MethodOffset.VertexBufferDrawState,  typeof(VertexBufferDrawState), 1),
             new TableItem(MethodOffset.DepthBiasState,         typeof(DepthBiasState),        1),
+            new TableItem(MethodOffset.ScissorState,           typeof(ScissorState),          8),
             new TableItem(MethodOffset.StencilBackMasks,       typeof(StencilBackMasks),      1),
             new TableItem(MethodOffset.RtDepthStencilState,    typeof(RtDepthStencilState),   1),
             new TableItem(MethodOffset.VertexAttribState,      typeof(VertexAttribState),     16),
diff --git a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
index 730ff40afa..0a720b2c48 100644
--- a/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
+++ b/Ryujinx.Graphics.Gpu/State/MethodOffset.cs
@@ -33,6 +33,7 @@ namespace Ryujinx.Graphics.Gpu.State
         ClearStencilValue               = 0x368,
         DepthBiasState                  = 0x370,
         TextureBarrier                  = 0x378,
+        ScissorState                    = 0x380,
         StencilBackMasks                = 0x3d5,
         InvalidateTextures              = 0x3dd,
         TextureBarrierTiled             = 0x3df,
diff --git a/Ryujinx.Graphics.Gpu/State/ScissorState.cs b/Ryujinx.Graphics.Gpu/State/ScissorState.cs
new file mode 100644
index 0000000000..0676664070
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/State/ScissorState.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gpu.State
+{
+    struct ScissorState
+    {
+        public Boolean32 Enable;
+        public ushort X1;
+        public ushort X2;
+        public ushort Y1;
+        public ushort Y2;
+        public uint Padding;
+    }
+}
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index c2a42370d0..f01e7c7ad5 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -31,10 +31,14 @@ namespace Ryujinx.Graphics.OpenGL
 
         private uint[] _componentMasks;
 
+        private readonly bool[] _scissorEnable;
+
         internal Pipeline()
         {
             _clipOrigin    = ClipOrigin.LowerLeft;
             _clipDepthMode = ClipDepthMode.NegativeOneToOne;
+
+            _scissorEnable = new bool[8];
         }
 
         public void Barrier()
@@ -674,6 +678,25 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
+        public void SetScissorEnable(int index, bool enable)
+        {
+            if (enable)
+            {
+                GL.Enable(IndexedEnableCap.ScissorTest, index);
+            }
+            else
+            {
+                GL.Disable(IndexedEnableCap.ScissorTest, index);
+            }
+
+            _scissorEnable[index] = enable;
+        }
+
+        public void SetScissor(int index, int x, int y, int width, int height)
+        {
+            GL.ScissorIndexed(index, x, y, width, height);
+        }
+
         public void SetStencilTest(StencilTestDescriptor stencilTest)
         {
             if (!stencilTest.TestEnable)
@@ -928,6 +951,17 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
+        public void RestoreScissorEnable()
+        {
+            for (int index = 0; index < 8; index++)
+            {
+                if (_scissorEnable[index])
+                {
+                    GL.Enable(IndexedEnableCap.ScissorTest, index);
+                }
+            }
+        }
+
         public void Dispose()
         {
             _framebuffer?.Dispose();
diff --git a/Ryujinx.Graphics.OpenGL/Renderer.cs b/Ryujinx.Graphics.OpenGL/Renderer.cs
index 6ff3c36c72..cf2adc45c2 100644
--- a/Ryujinx.Graphics.OpenGL/Renderer.cs
+++ b/Ryujinx.Graphics.OpenGL/Renderer.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.OpenGL
 {
     public sealed class Renderer : IRenderer
     {
-        private Pipeline _pipeline;
+        private readonly Pipeline _pipeline;
 
         public IPipeline Pipeline => _pipeline;
 
@@ -31,9 +31,9 @@ namespace Ryujinx.Graphics.OpenGL
 
             _counters = new Counters();
 
-            _window = new Window();
+            _window = new Window(this);
 
-            TextureCopy = new TextureCopy();
+            TextureCopy = new TextureCopy(this);
         }
 
         public IShader CompileShader(ShaderProgram shader)
diff --git a/Ryujinx.Graphics.OpenGL/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/TextureCopy.cs
index 27f1fd563b..ac9459d4aa 100644
--- a/Ryujinx.Graphics.OpenGL/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/TextureCopy.cs
@@ -6,9 +6,16 @@ namespace Ryujinx.Graphics.OpenGL
 {
     class TextureCopy : IDisposable
     {
+        private readonly Renderer _renderer;
+
         private int _srcFramebuffer;
         private int _dstFramebuffer;
 
+        public TextureCopy(Renderer renderer)
+        {
+            _renderer = renderer;
+        }
+
         public void Copy(
             TextureView src,
             TextureView dst,
@@ -34,6 +41,8 @@ namespace Ryujinx.Graphics.OpenGL
             GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
             GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
 
+            GL.Disable(EnableCap.ScissorTest);
+
             GL.BlitFramebuffer(
                 srcRegion.X1,
                 srcRegion.Y1,
@@ -48,6 +57,8 @@ namespace Ryujinx.Graphics.OpenGL
 
             GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+            ((Pipeline)_renderer.Pipeline).RestoreScissorEnable();
         }
 
         private static void Attach(FramebufferTarget target, Format format, int handle)
diff --git a/Ryujinx.Graphics.OpenGL/Window.cs b/Ryujinx.Graphics.OpenGL/Window.cs
index 2689a7c4a6..c9b7daeb95 100644
--- a/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/Ryujinx.Graphics.OpenGL/Window.cs
@@ -9,13 +9,17 @@ namespace Ryujinx.Graphics.OpenGL
         private const int NativeWidth  = 1280;
         private const int NativeHeight = 720;
 
+        private readonly Renderer _renderer;
+
         private int _width;
         private int _height;
 
         private int _copyFramebufferHandle;
 
-        public Window()
+        public Window(Renderer renderer)
         {
+            _renderer = renderer;
+
             _width  = NativeWidth;
             _height = NativeHeight;
         }
@@ -35,13 +39,13 @@ namespace Ryujinx.Graphics.OpenGL
             _height = height;
         }
 
-
         private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop)
         {
             bool[] oldFramebufferColorWritemask = new bool[4];
 
             int oldReadFramebufferHandle = GL.GetInteger(GetPName.ReadFramebufferBinding);
             int oldDrawFramebufferHandle = GL.GetInteger(GetPName.DrawFramebufferBinding);
+
             GL.GetBoolean(GetIndexedPName.ColorWritemask, drawFramebuffer, oldFramebufferColorWritemask);
 
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
@@ -55,6 +59,8 @@ namespace Ryujinx.Graphics.OpenGL
 
             GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
 
+            GL.Disable(EnableCap.ScissorTest);
+
             GL.Clear(ClearBufferMask.ColorBufferBit);
 
             int srcX0, srcX1, srcY0, srcY1;
@@ -119,6 +125,8 @@ namespace Ryujinx.Graphics.OpenGL
 
             GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, oldReadFramebufferHandle);
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, oldDrawFramebufferHandle);
+
+            ((Pipeline)_renderer.Pipeline).RestoreScissorEnable();
         }
 
         private int GetCopyFramebufferHandleLazy()