From 6e0799580f0d1b473a79471c5d365c6524d97a86 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Tue, 11 Jan 2022 16:15:17 -0300
Subject: [PATCH] Fix render target clear when sizes mismatch (#2994)

---
 Ryujinx.Graphics.GAL/IPipeline.cs             |  7 +-
 Ryujinx.Graphics.GAL/RectangleF.cs            | 12 ++--
 .../Engine/Threed/DrawManager.cs              | 69 +++++++++++++++++--
 .../Engine/Threed/StateUpdater.cs             | 33 ++++++++-
 .../Engine/Threed/ThreedClass.cs              |  8 +++
 .../Engine/Threed/ThreedClassState.cs         |  4 +-
 Ryujinx.Graphics.Gpu/Image/Texture.cs         | 14 +++-
 Ryujinx.Graphics.Gpu/Image/TextureManager.cs  | 43 ++++++++++++
 Ryujinx.Graphics.OpenGL/Pipeline.cs           | 16 +++--
 9 files changed, 181 insertions(+), 25 deletions(-)

diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index c02f84d47..75c3077eb 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -1,4 +1,3 @@
-using Ryujinx.Graphics.Shader;
 using System;
 
 namespace Ryujinx.Graphics.GAL
@@ -15,9 +14,9 @@ namespace Ryujinx.Graphics.GAL
 
         void ClearRenderTargetDepthStencil(
             float depthValue,
-            bool  depthMask,
-            int   stencilValue,
-            int   stencilMask);
+            bool depthMask,
+            int stencilValue,
+            int stencilMask);
 
         void CommandBufferBarrier();
 
diff --git a/Ryujinx.Graphics.GAL/RectangleF.cs b/Ryujinx.Graphics.GAL/RectangleF.cs
index c58aabf0e..cf1667812 100644
--- a/Ryujinx.Graphics.GAL/RectangleF.cs
+++ b/Ryujinx.Graphics.GAL/RectangleF.cs
@@ -2,16 +2,16 @@ namespace Ryujinx.Graphics.GAL
 {
     public struct RectangleF
     {
-        public float X      { get; }
-        public float Y      { get; }
-        public float Width  { get; }
+        public float X { get; }
+        public float Y { get; }
+        public float Width { get; }
         public float Height { get; }
 
         public RectangleF(float x, float y, float width, float height)
         {
-            X      = x;
-            Y      = y;
-            Width  = width;
+            X = x;
+            Y = y;
+            Width = width;
             Height = height;
         }
     }
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
index 8f69eaa74..b705d63e3 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/DrawManager.cs
@@ -1,5 +1,6 @@
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.Gpu.Engine.Types;
+using System;
 using System.Text;
 
 namespace Ryujinx.Graphics.Gpu.Engine.Threed
@@ -489,14 +490,62 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                 return;
             }
 
-            // Scissor and rasterizer discard also affect clears.
-            engine.UpdateState((1UL << StateUpdater.RasterizerStateIndex) | (1UL << StateUpdater.ScissorStateIndex));
-
             int index = (argument >> 6) & 0xf;
 
             engine.UpdateRenderTargetState(useControl: false, singleUse: index);
 
-            _channel.TextureManager.UpdateRenderTargets();
+            // If there is a mismatch on the host clip region and the one explicitly defined by the guest
+            // on the screen scissor state, then we need to force only one texture to be bound to avoid
+            // host clipping.
+            var screenScissorState = _state.State.ScreenScissorState;
+
+            // Must happen after UpdateRenderTargetState to have up-to-date clip region values.
+            bool clipMismatch = (screenScissorState.X | screenScissorState.Y) != 0 ||
+                                screenScissorState.Width != _channel.TextureManager.ClipRegionWidth ||
+                                screenScissorState.Height != _channel.TextureManager.ClipRegionHeight;
+
+            bool clearAffectedByStencilMask = (_state.State.ClearFlags & 1) != 0;
+            bool clearAffectedByScissor = (_state.State.ClearFlags & 0x100) != 0;
+            bool needsCustomScissor = !clearAffectedByScissor || clipMismatch;
+
+            // Scissor and rasterizer discard also affect clears.
+            ulong updateMask = 1UL << StateUpdater.RasterizerStateIndex;
+
+            if (!needsCustomScissor)
+            {
+                updateMask |= 1UL << StateUpdater.ScissorStateIndex;
+            }
+
+            engine.UpdateState(updateMask);
+
+            if (needsCustomScissor)
+            {
+                int scissorX = screenScissorState.X;
+                int scissorY = screenScissorState.Y;
+                int scissorW = screenScissorState.Width;
+                int scissorH = screenScissorState.Height;
+
+                if (clearAffectedByScissor)
+                {
+                    ref var scissorState = ref _state.State.ScissorState[0];
+
+                    scissorX = Math.Max(scissorX, scissorState.X1);
+                    scissorY = Math.Max(scissorY, scissorState.Y1);
+                    scissorW = Math.Min(scissorW, scissorState.X2 - scissorState.X1);
+                    scissorH = Math.Min(scissorH, scissorState.Y2 - scissorState.Y1);
+                }
+
+                _context.Renderer.Pipeline.SetScissor(0, true, scissorX, scissorY, scissorW, scissorH);
+            }
+
+            if (clipMismatch)
+            {
+                _channel.TextureManager.UpdateRenderTarget(index);
+            }
+            else
+            {
+                _channel.TextureManager.UpdateRenderTargets();
+            }
 
             bool clearDepth = (argument & 1) != 0;
             bool clearStencil = (argument & 2) != 0;
@@ -521,7 +570,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
 
                 if (clearStencil)
                 {
-                    stencilMask = _state.State.StencilTestState.FrontMask;
+                    stencilMask = clearAffectedByStencilMask ? _state.State.StencilTestState.FrontMask : 0xff;
+                }
+
+                if (clipMismatch)
+                {
+                    _channel.TextureManager.UpdateRenderTargetDepthStencil();
                 }
 
                 _context.Renderer.Pipeline.ClearRenderTargetDepthStencil(
@@ -531,6 +585,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     stencilMask);
             }
 
+            if (needsCustomScissor)
+            {
+                engine.UpdateScissorState();
+            }
+
             engine.UpdateRenderTargetState(useControl: true);
 
             if (renderEnable == ConditionalRenderEnabled.Host)
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
index 9f6ee17ce..1228a9440 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/StateUpdater.cs
@@ -339,6 +339,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             var scissor = _state.State.ScreenScissorState;
             Size sizeHint = new Size(scissor.X + scissor.Width, scissor.Y + scissor.Height, 1);
 
+            int clipRegionWidth = int.MaxValue;
+            int clipRegionHeight = int.MaxValue;
+
             bool changedScale = false;
 
             for (int index = 0; index < Constants.TotalRenderTargets; index++)
@@ -363,6 +366,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     sizeHint);
 
                 changedScale |= _channel.TextureManager.SetRenderTargetColor(index, color);
+
+                if (color != null)
+                {
+                    if (clipRegionWidth > color.Width)
+                    {
+                        clipRegionWidth = color.Width;
+                    }
+
+                    if (clipRegionHeight > color.Height)
+                    {
+                        clipRegionHeight = color.Height;
+                    }
+                }
             }
 
             bool dsEnable = _state.State.RtDepthStencilEnable;
@@ -381,6 +397,19 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     samplesInX,
                     samplesInY,
                     sizeHint);
+
+                if (depthStencil != null)
+                {
+                    if (clipRegionWidth > depthStencil.Width)
+                    {
+                        clipRegionWidth = depthStencil.Width;
+                    }
+
+                    if (clipRegionHeight > depthStencil.Height)
+                    {
+                        clipRegionHeight = depthStencil.Height;
+                    }
+                }
             }
 
             changedScale |= _channel.TextureManager.SetRenderTargetDepthStencil(depthStencil);
@@ -398,6 +427,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
                     UpdateScissorState();
                 }
             }
+
+            _channel.TextureManager.SetClipRegion(clipRegionWidth, clipRegionHeight);
         }
 
         /// <summary>
@@ -414,7 +445,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
         /// <summary>
         /// Updates host scissor test state based on current GPU state.
         /// </summary>
-        private void UpdateScissorState()
+        public void UpdateScissorState()
         {
             for (int index = 0; index < Constants.TotalViewports; index++)
             {
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
index f6de2730f..a2e8c64c1 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
@@ -137,6 +137,14 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
             _stateUpdater.UpdateRenderTargetState(useControl, singleUse);
         }
 
+        /// <summary>
+        /// Updates scissor based on current render target state.
+        /// </summary>
+        public void UpdateScissorState()
+        {
+            _stateUpdater.UpdateScissorState();
+        }
+
         /// <summary>
         /// Marks the entire state as dirty, forcing a full host state update before the next draw.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
index 9d8ad765d..a6a5a2ab9 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClassState.cs
@@ -754,7 +754,9 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
         public int DrawTextureTextureId;
         public int DrawTextureSrcX;
         public int DrawTextureSrcY;
-        public fixed uint Reserved10B0[44];
+        public fixed uint Reserved10B0[18];
+        public uint ClearFlags;
+        public fixed uint Reserved10FC[25];
         public Array16<VertexAttribState> VertexAttribState;
         public fixed uint Reserved11A0[31];
         public RtControl RtControl;
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index eacfa4f52..b2fa15a25 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -47,6 +47,16 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// </summary>
         public Target Target { get; private set; }
 
+        /// <summary>
+        /// Texture width.
+        /// </summary>
+        public int Width { get; private set; }
+
+        /// <summary>
+        /// Texture height.
+        /// </summary>
+        public int Height { get; private set; }
+
         /// <summary>
         /// Texture information.
         /// </summary>
@@ -926,7 +936,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 FlushTextureDataToGuest(tracked);
             }
         }
-        
+
         /// <summary>
         /// Gets a host texture to use for flushing the texture, at 1x resolution.
         /// If the HostTexture is already at 1x resolution, it is returned directly.
@@ -1322,6 +1332,8 @@ namespace Ryujinx.Graphics.Gpu.Image
         {
             Info = info;
             Target = info.Target;
+            Width = info.Width;
+            Height = info.Height;
             CanForceAnisotropy = CanTextureForceAnisotropy();
 
             _depth  = info.GetDepth();
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 70cb57d09..90e26442b 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -19,6 +19,9 @@ namespace Ryujinx.Graphics.Gpu.Image
         private Texture _rtDepthStencil;
         private ITexture _rtHostDs;
 
+        public int ClipRegionWidth { get; private set; }
+        public int ClipRegionHeight { get; private set; }
+
         /// <summary>
         /// The scaling factor applied to all currently bound render targets.
         /// </summary>
@@ -210,6 +213,17 @@ namespace Ryujinx.Graphics.Gpu.Image
             return changesScale || ScaleNeedsUpdated(depthStencil);
         }
 
+        /// <summary>
+        /// Sets the host clip region, which should be the intersection of all render target texture sizes.
+        /// </summary>
+        /// <param name="width">Width of the clip region, defined as the minimum width across all bound textures</param>
+        /// <param name="height">Height of the clip region, defined as the minimum height across all bound textures</param>
+        public void SetClipRegion(int width, int height)
+        {
+            ClipRegionWidth = width;
+            ClipRegionHeight = height;
+        }
+
         /// <summary>
         /// Gets the first available bound colour target, or the depth stencil target if not present.
         /// </summary>
@@ -409,6 +423,35 @@ namespace Ryujinx.Graphics.Gpu.Image
             }
         }
 
+        /// <summary>
+        /// Update host framebuffer attachments based on currently bound render target buffers.
+        /// </summary>
+        /// <remarks>
+        /// All attachments other than <paramref name="index"/> will be unbound.
+        /// </remarks>
+        /// <param name="index">Index of the render target color to be updated</param>
+        public void UpdateRenderTarget(int index)
+        {
+            new Span<ITexture>(_rtHostColors).Fill(null);
+            _rtHostColors[index] = _rtColors[index]?.HostTexture;
+
+            _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, null);
+        }
+
+        /// <summary>
+        /// Update host framebuffer attachments based on currently bound render target buffers.
+        /// </summary>
+        /// <remarks>
+        /// All color attachments will be unbound.
+        /// </remarks>
+        public void UpdateRenderTargetDepthStencil()
+        {
+            new Span<ITexture>(_rtHostColors).Fill(null);
+            _rtHostDs = _rtDepthStencil?.HostTexture;
+
+            _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
+        }
+
         /// <summary>
         /// Forces all textures, samplers, images and render targets to be rebound the next time
         /// CommitGraphicsBindings is called.
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 6d6e07457..ff5af42d1 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -6,7 +6,6 @@ using Ryujinx.Graphics.OpenGL.Queries;
 using Ryujinx.Graphics.Shader;
 using System;
 using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
 
 namespace Ryujinx.Graphics.OpenGL
 {
@@ -1058,14 +1057,17 @@ namespace Ryujinx.Graphics.OpenGL
 
                 _framebuffer.AttachColor(index, color);
 
-                int isBgra = color != null && color.Format.IsBgr() ? 1 : 0;
-
-                if (_fpIsBgra[index].X != isBgra)
+                if (color != null)
                 {
-                    _fpIsBgra[index].X = isBgra;
-                    isBgraChanged = true;
+                    int isBgra = color.Format.IsBgr() ? 1 : 0;
 
-                    RestoreComponentMask(index);
+                    if (_fpIsBgra[index].X != isBgra)
+                    {
+                        _fpIsBgra[index].X = isBgra;
+                        isBgraChanged = true;
+
+                        RestoreComponentMask(index);
+                    }
                 }
             }