From 9493cdfe553d77d8f37927ef2acf87cfbab1c467 Mon Sep 17 00:00:00 2001
From: riperiperi <rhy3756547@hotmail.com>
Date: Fri, 20 Nov 2020 20:14:45 +0000
Subject: [PATCH] Allow copy destination to have a different scale from source
 (#1711)

* Allow copy destination to have a different scale from source

Will result in more scaled copy destinations, but allows scaling in some games that copy textures to the output framebuffer.

* Support copying multiple levels/layers

Uses glFramebufferTextureLayer to copy multiple layers, copies levels individually (and scales the regions).

Remove CopyArrayScaled, since the backend copy handles it now.
---
 Ryujinx.Graphics.GAL/Extents2D.cs             |  13 +++
 .../Engine/MethodCopyTexture.cs               |  21 ++--
 Ryujinx.Graphics.Gpu/Image/Texture.cs         |  51 +--------
 Ryujinx.Graphics.Gpu/Image/TextureManager.cs  |   9 +-
 Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs  | 103 ++++++++++++------
 5 files changed, 99 insertions(+), 98 deletions(-)

diff --git a/Ryujinx.Graphics.GAL/Extents2D.cs b/Ryujinx.Graphics.GAL/Extents2D.cs
index e9e26af411..05b0ce6395 100644
--- a/Ryujinx.Graphics.GAL/Extents2D.cs
+++ b/Ryujinx.Graphics.GAL/Extents2D.cs
@@ -1,3 +1,5 @@
+using Ryujinx.Common;
+
 namespace Ryujinx.Graphics.GAL
 {
     public struct Extents2D
@@ -14,5 +16,16 @@ namespace Ryujinx.Graphics.GAL
             X2 = x2;
             Y2 = y2;
         }
+
+        public Extents2D Reduce(int level)
+        {
+            int div = 1 << level;
+            
+            return new Extents2D(
+                X1 >> level, 
+                Y1 >> level,
+                BitUtils.DivRoundUp(X2, div),
+                BitUtils.DivRoundUp(Y2, div));
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
index 430b733ee8..173f5fe479 100644
--- a/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/MethodCopyTexture.cs
@@ -71,12 +71,8 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 return;
             }
 
-            if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
-            {
-                srcTexture.PropagateScale(dstTexture);
-            }
-
-            float scale = srcTexture.ScaleFactor; // src and dest scales are identical now.
+            float scale = srcTexture.ScaleFactor;
+            float dstScale = dstTexture.ScaleFactor;
 
             Extents2D srcRegion = new Extents2D(
                 (int)Math.Ceiling(scale * (srcX1 / srcTexture.Info.SamplesInX)),
@@ -85,10 +81,10 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 (int)Math.Ceiling(scale * (srcY2 / srcTexture.Info.SamplesInY)));
 
             Extents2D dstRegion = new Extents2D(
-                (int)Math.Ceiling(scale * (dstX1 / dstTexture.Info.SamplesInX)),
-                (int)Math.Ceiling(scale * (dstY1 / dstTexture.Info.SamplesInY)),
-                (int)Math.Ceiling(scale * (dstX2 / dstTexture.Info.SamplesInX)),
-                (int)Math.Ceiling(scale * (dstY2 / dstTexture.Info.SamplesInY)));
+                (int)Math.Ceiling(dstScale * (dstX1 / dstTexture.Info.SamplesInX)),
+                (int)Math.Ceiling(dstScale * (dstY1 / dstTexture.Info.SamplesInY)),
+                (int)Math.Ceiling(dstScale * (dstX2 / dstTexture.Info.SamplesInX)),
+                (int)Math.Ceiling(dstScale * (dstY2 / dstTexture.Info.SamplesInY)));
 
             bool linearFilter = control.UnpackLinearFilter();
 
@@ -107,10 +103,7 @@ namespace Ryujinx.Graphics.Gpu.Engine
                 srcCopyTexture.Height++;
 
                 srcTexture = TextureManager.FindOrCreateTexture(srcCopyTexture, srcCopyTextureFormat, srcTexture.ScaleMode == TextureScaleMode.Scaled, srcHint);
-                if (srcTexture.ScaleFactor != dstTexture.ScaleFactor)
-                {
-                    srcTexture.PropagateScale(dstTexture);
-                }
+                scale = srcTexture.ScaleFactor;
 
                 srcRegion = new Extents2D(
                     (int)Math.Ceiling(scale * ((srcX1 / srcTexture.Info.SamplesInX) - srcTexture.Info.Width)),
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 21418a4b27..7fb41572db 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -430,48 +430,6 @@ namespace Ryujinx.Graphics.Gpu.Image
             }
         }
 
-        /// <summary>
-        /// Helper method for copying our Texture2DArray texture to the given target, with scaling.
-        /// This creates temporary views for each array layer on both textures, copying each one at a time.
-        /// </summary>
-        /// <param name="target">The texture array to copy to</param>
-        private void CopyArrayScaled(ITexture target)
-        {
-            TextureInfo viewInfo = new TextureInfo(
-                Info.Address,
-                Info.Width,
-                Info.Height,
-                1,
-                Info.Levels,
-                Info.SamplesInX,
-                Info.SamplesInY,
-                Info.Stride,
-                Info.IsLinear,
-                Info.GobBlocksInY,
-                Info.GobBlocksInZ,
-                Info.GobBlocksInTileX,
-                Target.Texture2D,
-                Info.FormatInfo,
-                Info.DepthStencilMode,
-                Info.SwizzleR,
-                Info.SwizzleG,
-                Info.SwizzleB,
-                Info.SwizzleA);
-
-            TextureCreateInfo createInfo = TextureManager.GetCreateInfo(viewInfo, _context.Capabilities, ScaleFactor);
-
-            for (int i = 0; i < Info.DepthOrLayers; i++)
-            {
-                ITexture from = HostTexture.CreateView(createInfo, i, 0);
-                ITexture to = target.CreateView(createInfo, i, 0);
-
-                from.CopyTo(to, new Extents2D(0, 0, from.Width, from.Height), new Extents2D(0, 0, to.Width, to.Height), true);
-
-                from.Release();
-                to.Release();
-            }
-        }
-
         /// <summary>
         /// Copy the host texture to a scaled one. If a texture is not provided, create it with the given scale.
         /// </summary>
@@ -486,14 +444,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 storage = _context.Renderer.CreateTexture(createInfo, scale);
             }
 
-            if (Info.Target == Target.Texture2DArray)
-            {
-                CopyArrayScaled(storage);
-            }
-            else
-            {
-                HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true);
-            }
+            HostTexture.CopyTo(storage, new Extents2D(0, 0, HostTexture.Width, HostTexture.Height), new Extents2D(0, 0, storage.Width, storage.Height), true);
 
             return storage;
         }
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index f1d31f4f44..8e64ca61f8 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -387,7 +387,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <returns>True if eligible</returns>
         public bool IsUpscaleCompatible(TextureInfo info)
         {
-            return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && info.Levels == 1 && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info);
+            return (info.Target == Target.Texture2D || info.Target == Target.Texture2DArray) && !info.FormatInfo.IsCompressed && UpscaleSafeMode(info);
         }
 
         /// <summary>
@@ -401,6 +401,13 @@ namespace Ryujinx.Graphics.Gpu.Image
             // While upscaling works for all targets defined by IsUpscaleCompatible, we additionally blacklist targets here that
             // may have undesirable results (upscaling blur textures) or simply waste GPU resources (upscaling texture atlas).
 
+            if (info.Levels > 3)
+            {
+                // Textures with more than 3 levels are likely to be game textures, rather than render textures.
+                // Small textures with full mips are likely to be removed by the next check.
+                return false;
+            }
+
             if (!(info.FormatInfo.Format.IsDepthOrStencil() || info.FormatInfo.Components == 1))
             {
                 // Discount square textures that aren't depth-stencil like. (excludes game textures, cubemap faces, most 3D texture LUT, texture atlas)
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index 74832dd8cf..20a3b91499 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -34,38 +34,61 @@ namespace Ryujinx.Graphics.OpenGL.Image
             GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
 
-            Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle);
-            Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle);
+            int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
+            int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers());
 
-            ClearBufferMask mask = GetMask(src.Format);
-
-            if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
+            for (int level = 0; level < levels; level++)
             {
-                linearFilter = false;
+                for (int layer = 0; layer < layers; layer++)
+                {
+                    if (layers > 1)
+                    {
+                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level, layer);
+                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer);
+                    }
+                    else
+                    {
+                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level);
+                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level);
+                    }
+
+                    ClearBufferMask mask = GetMask(src.Format);
+
+                    if ((mask & (ClearBufferMask.DepthBufferBit | ClearBufferMask.StencilBufferBit)) != 0 || src.Format.IsInteger())
+                    {
+                        linearFilter = false;
+                    }
+
+                    BlitFramebufferFilter filter = linearFilter
+                        ? BlitFramebufferFilter.Linear
+                        : BlitFramebufferFilter.Nearest;
+
+                    GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
+                    GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+
+                    GL.Disable(EnableCap.RasterizerDiscard);
+                    GL.Disable(IndexedEnableCap.ScissorTest, 0);
+
+                    GL.BlitFramebuffer(
+                        srcRegion.X1,
+                        srcRegion.Y1,
+                        srcRegion.X2,
+                        srcRegion.Y2,
+                        dstRegion.X1,
+                        dstRegion.Y1,
+                        dstRegion.X2,
+                        dstRegion.Y2,
+                        mask,
+                        filter);
+                }
+
+                if (level < levels - 1)
+                {
+                    srcRegion = srcRegion.Reduce(1);
+                    dstRegion = dstRegion.Reduce(1);
+                }
             }
 
-            BlitFramebufferFilter filter = linearFilter
-                ? BlitFramebufferFilter.Linear
-                : BlitFramebufferFilter.Nearest;
-
-            GL.ReadBuffer(ReadBufferMode.ColorAttachment0);
-            GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
-
-            GL.Disable(EnableCap.RasterizerDiscard);
-            GL.Disable(IndexedEnableCap.ScissorTest, 0);
-
-            GL.BlitFramebuffer(
-                srcRegion.X1,
-                srcRegion.Y1,
-                srcRegion.X2,
-                srcRegion.Y2,
-                dstRegion.X1,
-                dstRegion.Y1,
-                dstRegion.X2,
-                dstRegion.Y2,
-                mask,
-                filter);
-
             Attach(FramebufferTarget.ReadFramebuffer, src.Format, 0);
             Attach(FramebufferTarget.DrawFramebuffer, dst.Format, 0);
 
@@ -191,26 +214,40 @@ namespace Ryujinx.Graphics.OpenGL.Image
             }
         }
 
-        private static void Attach(FramebufferTarget target, Format format, int handle)
+        private static FramebufferAttachment AttachmentForFormat(Format format)
         {
             if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)
             {
-                GL.FramebufferTexture(target, FramebufferAttachment.DepthStencilAttachment, handle, 0);
+                return FramebufferAttachment.DepthStencilAttachment;
             }
             else if (IsDepthOnly(format))
             {
-                GL.FramebufferTexture(target, FramebufferAttachment.DepthAttachment, handle, 0);
+                return FramebufferAttachment.DepthAttachment;
             }
             else if (format == Format.S8Uint)
             {
-                GL.FramebufferTexture(target, FramebufferAttachment.StencilAttachment, handle, 0);
+                return FramebufferAttachment.StencilAttachment;
             }
             else
             {
-                GL.FramebufferTexture(target, FramebufferAttachment.ColorAttachment0, handle, 0);
+                return FramebufferAttachment.ColorAttachment0;
             }
         }
 
+        private static void Attach(FramebufferTarget target, Format format, int handle, int level = 0)
+        {
+            FramebufferAttachment attachment = AttachmentForFormat(format);
+
+            GL.FramebufferTexture(target, attachment, handle, level);
+        }
+
+        private static void Attach(FramebufferTarget target, Format format, int handle, int level, int layer)
+        {
+            FramebufferAttachment attachment = AttachmentForFormat(format);
+
+            GL.FramebufferTextureLayer(target, attachment, handle, level, layer);
+        }
+
         private static ClearBufferMask GetMask(Format format)
         {
             if (format == Format.D24UnormS8Uint || format == Format.D32FloatS8Uint)