From a3e7bb8eb40b66e61a5a3bfef0b780d0c76a31c1 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Sun, 5 Jun 2022 14:06:47 -0300
Subject: [PATCH] Copy dependency for multisample and non-multisample textures
 (#3382)

* Use copy dependency for textures that differs in multisample but are otherwise compatible

* Remove allowMs flag as it's no longer required for correctness, it's just an optimization now

* Dispose intermmediate pool
---
 Ryujinx.Graphics.GAL/Target.cs                |  8 ++
 Ryujinx.Graphics.Gpu/Image/Texture.cs         | 20 +---
 Ryujinx.Graphics.Gpu/Image/TextureCache.cs    |  2 -
 .../Image/TextureCompatibility.cs             |  6 +-
 .../Image/IntermmediatePool.cs                | 98 +++++++++++++++++++
 Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs  | 51 ++++++++--
 Ryujinx.Graphics.OpenGL/Image/TextureView.cs  | 69 ++++++++++++-
 7 files changed, 225 insertions(+), 29 deletions(-)
 create mode 100644 Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs

diff --git a/Ryujinx.Graphics.GAL/Target.cs b/Ryujinx.Graphics.GAL/Target.cs
index a62d628a..e20bd3c8 100644
--- a/Ryujinx.Graphics.GAL/Target.cs
+++ b/Ryujinx.Graphics.GAL/Target.cs
@@ -13,4 +13,12 @@ namespace Ryujinx.Graphics.GAL
         CubemapArray,
         TextureBuffer
     }
+
+    public static class TargetExtensions
+    {
+        public static bool IsMultisample(this Target target)
+        {
+            return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
+        }
+    }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index cfb7a3b7..aadb4260 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -1136,32 +1136,22 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <param name="range">Texture view physical memory ranges</param>
         /// <param name="layerSize">Layer size on the given texture</param>
         /// <param name="caps">Host GPU capabilities</param>
-        /// <param name="allowMs">Indicates that multisample textures are allowed to match non-multisample requested textures</param>
         /// <param name="firstLayer">Texture view initial layer on this texture</param>
         /// <param name="firstLevel">Texture view first mipmap level on this texture</param>
         /// <returns>The level of compatiblilty a view with the given parameters created from this texture has</returns>
-        public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, bool allowMs, out int firstLayer, out int firstLevel)
+        public TextureViewCompatibility IsViewCompatible(TextureInfo info, MultiRange range, int layerSize, Capabilities caps, out int firstLayer, out int firstLevel)
         {
             TextureViewCompatibility result = TextureViewCompatibility.Full;
 
             result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewFormatCompatible(Info, info, caps));
             if (result != TextureViewCompatibility.Incompatible)
             {
-                bool msTargetCompatible = false;
+                result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
 
-                if (allowMs)
+                bool bothMs = Info.Target.IsMultisample() && info.Target.IsMultisample();
+                if (bothMs && (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY))
                 {
-                    msTargetCompatible = Info.Target == Target.Texture2DMultisample && info.Target == Target.Texture2D;
-                }
-
-                if (!msTargetCompatible)
-                {
-                    result = TextureCompatibility.PropagateViewCompatibility(result, TextureCompatibility.ViewTargetCompatible(Info, info));
-
-                    if (Info.SamplesInX != info.SamplesInX || Info.SamplesInY != info.SamplesInY)
-                    {
-                        result = TextureViewCompatibility.Incompatible;
-                    }
+                    result = TextureViewCompatibility.Incompatible;
                 }
 
                 if (result == TextureViewCompatibility.Full && Info.FormatInfo.Format != info.FormatInfo.Format && !_context.Capabilities.SupportsMismatchingViewFormat)
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index 4fa80c95..04541057 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -547,7 +547,6 @@ namespace Ryujinx.Graphics.Gpu.Image
                     range.Value,
                     sizeInfo.LayerSize,
                     _context.Capabilities,
-                    flags.HasFlag(TextureSearchFlags.ForCopy),
                     out int firstLayer,
                     out int firstLevel);
 
@@ -662,7 +661,6 @@ namespace Ryujinx.Graphics.Gpu.Image
                         overlap.Range,
                         overlap.LayerSize,
                         _context.Capabilities,
-                        false,
                         out int firstLayer,
                         out int firstLevel);
 
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
index b798441f..61b48dc4 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCompatibility.cs
@@ -2,7 +2,6 @@ using Ryujinx.Common;
 using Ryujinx.Graphics.GAL;
 using Ryujinx.Graphics.Texture;
 using System;
-using System.Numerics;
 
 namespace Ryujinx.Graphics.Gpu.Image
 {
@@ -657,6 +656,11 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                 case Target.Texture2DMultisample:
                 case Target.Texture2DMultisampleArray:
+                    if (rhs.Target == Target.Texture2D || rhs.Target == Target.Texture2DArray)
+                    {
+                        return TextureViewCompatibility.CopyOnly;
+                    }
+
                     result = rhs.Target == Target.Texture2DMultisample ||
                              rhs.Target == Target.Texture2DMultisampleArray;
                     break;
diff --git a/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs b/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
new file mode 100644
index 00000000..bd6efc76
--- /dev/null
+++ b/Ryujinx.Graphics.OpenGL/Image/IntermmediatePool.cs
@@ -0,0 +1,98 @@
+using Ryujinx.Graphics.GAL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.OpenGL.Image
+{
+    class IntermmediatePool : IDisposable
+    {
+        private readonly Renderer _renderer;
+        private readonly List<TextureView> _entries;
+
+        public IntermmediatePool(Renderer renderer)
+        {
+            _renderer = renderer;
+            _entries = new List<TextureView>();
+        }
+
+        public TextureView GetOrCreateWithAtLeast(
+            Target target,
+            int blockWidth,
+            int blockHeight,
+            int bytesPerPixel,
+            Format format,
+            int width,
+            int height,
+            int depth,
+            int levels)
+        {
+            TextureView entry;
+
+            for (int i = 0; i < _entries.Count; i++)
+            {
+                entry = _entries[i];
+
+                if (entry.Target == target && entry.Format == format)
+                {
+                    if (entry.Width < width || entry.Height < height || entry.Info.Depth < depth || entry.Info.Levels < levels)
+                    {
+                        width = Math.Max(width, entry.Width);
+                        height = Math.Max(height, entry.Height);
+                        depth = Math.Max(depth, entry.Info.Depth);
+                        levels = Math.Max(levels, entry.Info.Levels);
+
+                        entry.Dispose();
+                        entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
+                        _entries[i] = entry;
+                    }
+
+                    return entry;
+                }
+            }
+
+            entry = CreateNew(target, blockWidth, blockHeight, bytesPerPixel, format, width, height, depth, levels);
+            _entries.Add(entry);
+
+            return entry;
+        }
+
+        private TextureView CreateNew(
+            Target target,
+            int blockWidth,
+            int blockHeight,
+            int bytesPerPixel,
+            Format format,
+            int width,
+            int height,
+            int depth,
+            int levels)
+        {
+            return (TextureView)_renderer.CreateTexture(new TextureCreateInfo(
+                width,
+                height,
+                depth,
+                levels,
+                1,
+                blockWidth,
+                blockHeight,
+                bytesPerPixel,
+                format,
+                DepthStencilMode.Depth,
+                target,
+                SwizzleComponent.Red,
+                SwizzleComponent.Green,
+                SwizzleComponent.Blue,
+                SwizzleComponent.Alpha), 1f);
+        }
+
+        public void Dispose()
+        {
+            foreach (TextureView entry in _entries)
+            {
+                entry.Dispose();
+            }
+
+            _entries.Clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
index 9be86561..44804d43 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureCopy.cs
@@ -9,6 +9,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
     {
         private readonly Renderer _renderer;
 
+        public IntermmediatePool IntermmediatePool { get; }
+
         private int _srcFramebuffer;
         private int _dstFramebuffer;
 
@@ -18,6 +20,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
         public TextureCopy(Renderer renderer)
         {
             _renderer = renderer;
+            IntermmediatePool = new IntermmediatePool(renderer);
         }
 
         public void Copy(
@@ -25,7 +28,30 @@ namespace Ryujinx.Graphics.OpenGL.Image
             TextureView dst,
             Extents2D   srcRegion,
             Extents2D   dstRegion,
-            bool        linearFilter)
+            bool        linearFilter,
+            int         srcLayer = 0,
+            int         dstLayer = 0,
+            int         srcLevel = 0,
+            int         dstLevel = 0)
+        {
+            int levels = Math.Min(src.Info.Levels - srcLevel, dst.Info.Levels - dstLevel);
+            int layers = Math.Min(src.Info.GetLayers() - srcLayer, dst.Info.GetLayers() - dstLayer);
+
+            Copy(src, dst, srcRegion, dstRegion, linearFilter, srcLayer, dstLayer, srcLevel, dstLevel, layers, levels);
+        }
+
+        public void Copy(
+            TextureView src,
+            TextureView dst,
+            Extents2D   srcRegion,
+            Extents2D   dstRegion,
+            bool        linearFilter,
+            int         srcLayer,
+            int         dstLayer,
+            int         srcLevel,
+            int         dstLevel,
+            int         layers,
+            int         levels)
         {
             TextureView srcConverted = src.Format.IsBgr() != dst.Format.IsBgr() ? BgraSwap(src) : src;
 
@@ -34,22 +60,29 @@ namespace Ryujinx.Graphics.OpenGL.Image
             GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, GetSrcFramebufferLazy());
             GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, GetDstFramebufferLazy());
 
-            int levels = Math.Min(src.Info.Levels, dst.Info.Levels);
-            int layers = Math.Min(src.Info.GetLayers(), dst.Info.GetLayers());
+            if (srcLevel != 0)
+            {
+                srcRegion = srcRegion.Reduce(srcLevel);
+            }
+
+            if (dstLevel != 0)
+            {
+                dstRegion = dstRegion.Reduce(dstLevel);
+            }
 
             for (int level = 0; level < levels; level++)
             {
                 for (int layer = 0; layer < layers; layer++)
                 {
-                    if (layers > 1)
+                    if ((srcLayer | dstLayer) != 0 || layers > 1)
                     {
-                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level, layer);
-                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level, layer);
+                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level, srcLayer + layer);
+                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level, dstLayer + layer);
                     }
                     else
                     {
-                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, level);
-                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, level);
+                        Attach(FramebufferTarget.ReadFramebuffer, src.Format, srcConverted.Handle, srcLevel + level);
+                        Attach(FramebufferTarget.DrawFramebuffer, dst.Format, dst.Handle, dstLevel + level);
                     }
 
                     ClearBufferMask mask = GetMask(src.Format);
@@ -484,6 +517,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
 
                 _copyPboHandle = 0;
             }
+
+            IntermmediatePool.Dispose();
         }
     }
 }
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 909a0620..afb9a278 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -115,14 +115,77 @@ namespace Ryujinx.Graphics.OpenGL.Image
         {
             TextureView destinationView = (TextureView)destination;
 
-            _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
+            if (destinationView.Target.IsMultisample() || Target.IsMultisample())
+            {
+                Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
+                Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
+
+                TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
+                    GetIntermmediateTarget(Target),
+                    Info.BlockWidth,
+                    Info.BlockHeight,
+                    Info.BytesPerPixel,
+                    Format,
+                    Width,
+                    Height,
+                    Info.Depth,
+                    Info.Levels);
+
+                GL.Disable(EnableCap.FramebufferSrgb);
+
+                _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true);
+                _renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, firstLayer, 0, firstLevel);
+
+                GL.Enable(EnableCap.FramebufferSrgb);
+            }
+            else
+            {
+                _renderer.TextureCopy.CopyUnscaled(this, destinationView, 0, firstLayer, 0, firstLevel);
+            }
         }
 
         public void CopyTo(ITexture destination, int srcLayer, int dstLayer, int srcLevel, int dstLevel)
         {
-             TextureView destinationView = (TextureView)destination;
+            TextureView destinationView = (TextureView)destination;
 
-            _renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
+            if (destinationView.Target.IsMultisample() || Target.IsMultisample())
+            {
+                Extents2D srcRegion = new Extents2D(0, 0, Width, Height);
+                Extents2D dstRegion = new Extents2D(0, 0, destinationView.Width, destinationView.Height);
+
+                TextureView intermmediate = _renderer.TextureCopy.IntermmediatePool.GetOrCreateWithAtLeast(
+                    GetIntermmediateTarget(Target),
+                    Info.BlockWidth,
+                    Info.BlockHeight,
+                    Info.BytesPerPixel,
+                    Format,
+                    Math.Max(1, Width >> srcLevel),
+                    Math.Max(1, Height >> srcLevel),
+                    1,
+                    1);
+
+                GL.Disable(EnableCap.FramebufferSrgb);
+
+                _renderer.TextureCopy.Copy(this, intermmediate, srcRegion, srcRegion, true, srcLayer, 0, srcLevel, 0, 1, 1);
+                _renderer.TextureCopy.Copy(intermmediate, destinationView, srcRegion, dstRegion, true, 0, dstLayer, 0, dstLevel, 1, 1);
+
+                GL.Enable(EnableCap.FramebufferSrgb);
+            }
+            else
+            {
+                _renderer.TextureCopy.CopyUnscaled(this, destinationView, srcLayer, dstLayer, srcLevel, dstLevel, 1, 1);
+            }
+        }
+
+        private static Target GetIntermmediateTarget(Target srcTarget)
+        {
+            return srcTarget switch
+            {
+                Target.Texture2D => Target.Texture2DMultisample,
+                Target.Texture2DArray => Target.Texture2DMultisampleArray,
+                Target.Texture2DMultisampleArray => Target.Texture2DArray,
+                _ => Target.Texture2D
+            };
         }
 
         public void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter)