From d786d8d2b924da7cd116a2eb97d738a9f07b4e43 Mon Sep 17 00:00:00 2001
From: gdk <gab.dark.100@gmail.com>
Date: Wed, 30 Oct 2019 20:45:01 -0300
Subject: [PATCH] Support copy of slices to 3D textures, remove old 3D render
 target layered render support, do not delete textures with existing views
 created from them

---
 Ryujinx.Graphics.GAL/IPipeline.cs            |  1 -
 Ryujinx.Graphics.GAL/ITexture.cs             |  2 +-
 Ryujinx.Graphics.Gpu/Engine/Methods.cs       | 84 ++++----------------
 Ryujinx.Graphics.Gpu/Image/Texture.cs        | 62 ++++++++++++---
 Ryujinx.Graphics.Gpu/Image/TextureManager.cs | 67 +++++++---------
 Ryujinx.Graphics.OpenGL/Framebuffer.cs       | 10 ---
 Ryujinx.Graphics.OpenGL/Pipeline.cs          | 22 -----
 Ryujinx.Graphics.OpenGL/TextureView.cs       |  4 +-
 Ryujinx.Graphics.Texture/SizeCalculator.cs   | 39 +++++++--
 9 files changed, 131 insertions(+), 160 deletions(-)

diff --git a/Ryujinx.Graphics.GAL/IPipeline.cs b/Ryujinx.Graphics.GAL/IPipeline.cs
index c331373b1..770606895 100644
--- a/Ryujinx.Graphics.GAL/IPipeline.cs
+++ b/Ryujinx.Graphics.GAL/IPipeline.cs
@@ -61,7 +61,6 @@ namespace Ryujinx.Graphics.GAL
 
         void SetRenderTargetColorMasks(uint[] componentMask);
 
-        void SetRenderTargets(ITexture color3D, ITexture depthStencil);
         void SetRenderTargets(ITexture[] colors, ITexture depthStencil);
 
         void SetStencilTest(StencilTestDescriptor stencilTest);
diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs
index 6b3f5c59e..f170e3741 100644
--- a/Ryujinx.Graphics.GAL/ITexture.cs
+++ b/Ryujinx.Graphics.GAL/ITexture.cs
@@ -7,7 +7,7 @@ namespace Ryujinx.Graphics.GAL
     {
         int Handle { get; }
 
-        void CopyTo(ITexture destination);
+        void CopyTo(ITexture destination, int firstLayer, int firstLevel);
         void CopyTo(ITexture destination, Extents2D srcRegion, Extents2D dstRegion, bool linearFilter);
 
         ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Methods.cs b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
index 5d90573c7..5d1563129 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Methods.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Methods.cs
@@ -227,39 +227,28 @@ namespace Ryujinx.Graphics.Gpu.Engine
             int samplesInX = msaaMode.SamplesInX();
             int samplesInY = msaaMode.SamplesInY();
 
-            Image.Texture color3D = Get3DRenderTarget(samplesInX, samplesInY);
-
-            if (color3D == null)
+            for (int index = 0; index < Constants.TotalRenderTargets; index++)
             {
-                for (int index = 0; index < Constants.TotalRenderTargets; index++)
+                var colorState = _context.State.Get<RtColorState>(MethodOffset.RtColorState, index);
+
+                if (!IsRtEnabled(colorState))
                 {
-                    var colorState = _context.State.Get<RtColorState>(MethodOffset.RtColorState, index);
+                    _textureManager.SetRenderTargetColor(index, null);
 
-                    if (!IsRtEnabled(colorState))
-                    {
-                        _textureManager.SetRenderTargetColor(index, null);
-
-                        continue;
-                    }
-
-                    Image.Texture color = _textureManager.FindOrCreateTexture(
-                        colorState,
-                        samplesInX,
-                        samplesInY);
-
-                    _textureManager.SetRenderTargetColor(index, color);
-
-                    if (color != null)
-                    {
-                        color.Modified = true;
-                    }
+                    continue;
                 }
-            }
-            else
-            {
-                _textureManager.SetRenderTargetColor3D(color3D);
 
-                color3D.Modified = true;
+                Image.Texture color = _textureManager.FindOrCreateTexture(
+                    colorState,
+                    samplesInX,
+                    samplesInY);
+
+                _textureManager.SetRenderTargetColor(index, color);
+
+                if (color != null)
+                {
+                    color.Modified = true;
+                }
             }
 
             bool dsEnable = _context.State.Get<Boolean32>(MethodOffset.RtDepthStencilEnable);
@@ -286,45 +275,6 @@ namespace Ryujinx.Graphics.Gpu.Engine
             }
         }
 
-        private Image.Texture Get3DRenderTarget(int samplesInX, int samplesInY)
-        {
-            var colorState0 = _context.State.Get<RtColorState>(MethodOffset.RtColorState, 0);
-
-            if (!IsRtEnabled(colorState0) || !colorState0.MemoryLayout.UnpackIsTarget3D() || colorState0.Depth != 1)
-            {
-                return null;
-            }
-
-            int slices = 1;
-            int unused = 0;
-
-            for (int index = 1; index < Constants.TotalRenderTargets; index++)
-            {
-                var colorState = _context.State.Get<RtColorState>(MethodOffset.RtColorState, index);
-
-                if (!IsRtEnabled(colorState))
-                {
-                    unused++;
-
-                    continue;
-                }
-
-                if (colorState.MemoryLayout.UnpackIsTarget3D() && colorState.Depth == 1)
-                {
-                    slices++;
-                }
-            }
-
-            if (slices + unused == Constants.TotalRenderTargets)
-            {
-                colorState0.Depth = slices;
-
-                return _textureManager.FindOrCreateTexture(colorState0, samplesInX, samplesInY);
-            }
-
-            return null;
-        }
-
         private static bool IsRtEnabled(RtColorState colorState)
         {
             // Colors are disabled by writing 0 to the format.
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index c34ff1d37..cc7e7bf60 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -6,6 +6,7 @@ using Ryujinx.Graphics.Texture;
 using Ryujinx.Graphics.Texture.Astc;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 
 namespace Ryujinx.Graphics.Gpu.Image
 {
@@ -116,6 +117,8 @@ namespace Ryujinx.Graphics.Gpu.Image
             _views.Remove(texture);
 
             texture._viewStorage = null;
+
+            DeleteIfNotUsed();
         }
 
         public void ChangeSize(int width, int height, int depthOrLayers)
@@ -187,7 +190,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             {
                 ITexture newStorage = _context.Renderer.CreateTexture(createInfo);
 
-                HostTexture.CopyTo(newStorage);
+                HostTexture.CopyTo(newStorage, 0, 0);
 
                 ReplaceStorage(newStorage);
             }
@@ -413,7 +416,21 @@ namespace Ryujinx.Graphics.Gpu.Image
                    _info.SamplesInY == info.SamplesInY;
         }
 
-        public bool IsViewCompatible(TextureInfo info, ulong size, out int firstLayer, out int firstLevel)
+        public bool IsViewCompatible(
+            TextureInfo info,
+            ulong       size,
+            out int     firstLayer,
+            out int     firstLevel)
+        {
+            return IsViewCompatible(info, size, isCopy: false, out firstLayer, out firstLevel);
+        }
+
+        public bool IsViewCompatible(
+            TextureInfo info,
+            ulong       size,
+            bool        isCopy,
+            out int     firstLayer,
+            out int     firstLevel)
         {
             // Out of range.
             if (info.Address < Address || info.Address + size > EndAddress)
@@ -441,12 +458,12 @@ namespace Ryujinx.Graphics.Gpu.Image
                 return false;
             }
 
-            if (!ViewSizeMatches(info, firstLevel))
+            if (!ViewSizeMatches(info, firstLevel, isCopy))
             {
                 return false;
             }
 
-            if (!ViewTargetCompatible(info))
+            if (!ViewTargetCompatible(info, isCopy))
             {
                 return false;
             }
@@ -496,18 +513,24 @@ namespace Ryujinx.Graphics.Gpu.Image
             return TextureCompatibility.FormatCompatible(_info.FormatInfo, info.FormatInfo);
         }
 
-        private bool ViewSizeMatches(TextureInfo info, int level)
+        private bool ViewSizeMatches(TextureInfo info, int level, bool isCopy)
         {
             Size size = GetAlignedSize(_info, level);
 
             Size otherSize = GetAlignedSize(info);
 
-            return size.Width  == otherSize.Width  &&
-                   size.Height == otherSize.Height &&
-                   size.Depth  == otherSize.Depth;
+            // For copies, we can copy a subset of the 3D texture slices,
+            // so the depth may be different in this case.
+            if (!isCopy && info.Target == Target.Texture3D && size.Depth != otherSize.Depth)
+            {
+                return false;
+            }
+
+            return size.Width  == otherSize.Width &&
+                   size.Height == otherSize.Height;
         }
 
-        private bool ViewTargetCompatible(TextureInfo info)
+        private bool ViewTargetCompatible(TextureInfo info, bool isCopy)
         {
             switch (_info.Target)
             {
@@ -534,7 +557,8 @@ namespace Ryujinx.Graphics.Gpu.Image
                            info.Target == Target.Texture2DMultisampleArray;
 
                 case Target.Texture3D:
-                    return info.Target == Target.Texture3D;
+                    return info.Target == Target.Texture3D ||
+                          (info.Target == Target.Texture2D && isCopy);
             }
 
             return false;
@@ -686,7 +710,9 @@ namespace Ryujinx.Graphics.Gpu.Image
 
         public void DecrementReferenceCount()
         {
-            if (--_referenceCount == 0)
+            int newRefCount = --_referenceCount;
+
+            if (newRefCount == 0)
             {
                 if (_viewStorage != this)
                 {
@@ -694,7 +720,21 @@ namespace Ryujinx.Graphics.Gpu.Image
                 }
 
                 _context.Methods.TextureManager.RemoveTextureFromCache(this);
+            }
 
+            Debug.Assert(newRefCount >= 0);
+
+            DeleteIfNotUsed();
+        }
+
+        private void DeleteIfNotUsed()
+        {
+            // We can delete the texture as long it is not being used
+            // in any cache (the reference count is 0 in this case), and
+            // also all views that may be created from this texture were
+            // already deleted (views count is 0).
+            if (_referenceCount == 0 && _views.Count == 0)
+            {
                 DisposeTextures();
             }
         }
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 7cf79d0f9..2db1b4f27 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -17,13 +17,10 @@ namespace Ryujinx.Graphics.Gpu.Image
         private TextureBindingsManager _gpBindingsManager;
 
         private Texture[] _rtColors;
-        private Texture   _rtColor3D;
-
-        private Texture _rtDepthStencil;
+        private Texture   _rtDepthStencil;
 
         private ITexture[] _rtHostColors;
-
-        private ITexture _rtHostDs;
+        private ITexture   _rtHostDs;
 
         private RangeList<Texture> _textures;
 
@@ -98,13 +95,6 @@ namespace Ryujinx.Graphics.Gpu.Image
         public void SetRenderTargetColor(int index, Texture color)
         {
             _rtColors[index] = color;
-
-            _rtColor3D = null;
-        }
-
-        public void SetRenderTargetColor3D(Texture color)
-        {
-            _rtColor3D = color;
         }
 
         public void SetRenderTargetDepthStencil(Texture depthStencil)
@@ -141,38 +131,21 @@ namespace Ryujinx.Graphics.Gpu.Image
                 anyChanged = true;
             }
 
-            if (_rtColor3D == null)
+            for (int index = 0; index < _rtColors.Length; index++)
             {
-                for (int index = 0; index < _rtColors.Length; index++)
-                {
-                    ITexture hostTexture = _rtColors[index]?.HostTexture;
+                ITexture hostTexture = _rtColors[index]?.HostTexture;
 
-                    if (_rtHostColors[index] != hostTexture)
-                    {
-                        _rtHostColors[index] = hostTexture;
-
-                        anyChanged = true;
-                    }
-                }
-
-                if (anyChanged)
+                if (_rtHostColors[index] != hostTexture)
                 {
-                    _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
-                }
-            }
-            else
-            {
-                if (_rtHostColors[0] != _rtColor3D.HostTexture)
-                {
-                    _rtHostColors[0] = _rtColor3D.HostTexture;
+                    _rtHostColors[index] = hostTexture;
 
                     anyChanged = true;
                 }
+            }
 
-                if (anyChanged)
-                {
-                    _context.Renderer.Pipeline.SetRenderTargets(_rtColor3D.HostTexture, _rtHostDs);
-                }
+            if (anyChanged)
+            {
+                _context.Renderer.Pipeline.SetRenderTargets(_rtHostColors, _rtHostDs);
             }
         }
 
@@ -447,11 +420,29 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                         ITexture newView = texture.HostTexture.CreateView(createInfo, firstLayer, firstLevel);
 
-                        overlap.HostTexture.CopyTo(newView);
+                        overlap.HostTexture.CopyTo(newView, 0, 0);
 
                         overlap.ReplaceView(texture, overlapInfo, newView);
                     }
                 }
+
+                // If the texture is a 3D texture, we need to additionally copy any slice
+                // of the 3D texture to the newly created 3D texture.
+                if (info.Target == Target.Texture3D)
+                {
+                    foreach (Texture overlap in overlaps)
+                    {
+                        if (texture.IsViewCompatible(
+                            overlap.Info,
+                            overlap.Size,
+                            isCopy: true,
+                            out int firstLayer,
+                            out int firstLevel))
+                        {
+                            overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel);
+                        }
+                    }
+                }
             }
 
             // Sampler textures are managed by the texture pool, all other textures
diff --git a/Ryujinx.Graphics.OpenGL/Framebuffer.cs b/Ryujinx.Graphics.OpenGL/Framebuffer.cs
index 409130094..d416bd03f 100644
--- a/Ryujinx.Graphics.OpenGL/Framebuffer.cs
+++ b/Ryujinx.Graphics.OpenGL/Framebuffer.cs
@@ -29,16 +29,6 @@ namespace Ryujinx.Graphics.OpenGL
                 0);
         }
 
-        public void AttachColor(int index, TextureView color, int layer)
-        {
-            GL.FramebufferTextureLayer(
-                FramebufferTarget.Framebuffer,
-                FramebufferAttachment.ColorAttachment0 + index,
-                color?.Handle ?? 0,
-                0,
-                layer);
-        }
-
         public void AttachDepthStencil(TextureView depthStencil)
         {
             // Detach the last depth/stencil buffer if there is any.
diff --git a/Ryujinx.Graphics.OpenGL/Pipeline.cs b/Ryujinx.Graphics.OpenGL/Pipeline.cs
index 5df801f87..745926e74 100644
--- a/Ryujinx.Graphics.OpenGL/Pipeline.cs
+++ b/Ryujinx.Graphics.OpenGL/Pipeline.cs
@@ -697,28 +697,6 @@ namespace Ryujinx.Graphics.OpenGL
             }
         }
 
-        public void SetRenderTargets(ITexture color3D, ITexture depthStencil)
-        {
-            EnsureFramebuffer();
-
-            TextureView color = (TextureView)color3D;
-
-            for (int index = 0; index < color.DepthOrLayers; index++)
-            {
-                _framebuffer.AttachColor(index, color, index);
-            }
-
-            TextureView depthStencilView = (TextureView)depthStencil;
-
-            _framebuffer.AttachDepthStencil(depthStencilView);
-
-            _framebuffer.SetDrawBuffers(color.DepthOrLayers);
-
-            _hasDepthBuffer = depthStencil != null && depthStencilView.Format != Format.S8Uint;
-
-            UpdateDepthTest();
-        }
-
         public void SetRenderTargets(ITexture[] colors, ITexture depthStencil)
         {
             EnsureFramebuffer();
diff --git a/Ryujinx.Graphics.OpenGL/TextureView.cs b/Ryujinx.Graphics.OpenGL/TextureView.cs
index 8a2b50dc4..8fced290d 100644
--- a/Ryujinx.Graphics.OpenGL/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/TextureView.cs
@@ -137,11 +137,11 @@ namespace Ryujinx.Graphics.OpenGL
             return _parent.GetHashCode();
         }
 
-        public void CopyTo(ITexture destination)
+        public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
         {
             TextureView destinationView = (TextureView)destination;
 
-            TextureCopyUnscaled.Copy(this, destinationView, 0, 0);
+            TextureCopyUnscaled.Copy(this, destinationView, firstLayer, firstLevel);
 
             int width  = Math.Min(Width,  destinationView.Width);
             int height = Math.Min(Height, destinationView.Height);
diff --git a/Ryujinx.Graphics.Texture/SizeCalculator.cs b/Ryujinx.Graphics.Texture/SizeCalculator.cs
index 873f41a32..11385d28d 100644
--- a/Ryujinx.Graphics.Texture/SizeCalculator.cs
+++ b/Ryujinx.Graphics.Texture/SizeCalculator.cs
@@ -22,8 +22,11 @@ namespace Ryujinx.Graphics.Texture
             int gobBlocksInZ,
             int gobBlocksInTileX)
         {
+            bool is3D = depth > 1;
+
             int layerSize = 0;
 
+            int[] allOffsets = new int[levels * layers * depth];
             int[] mipOffsets = new int[levels];
 
             int mipGobBlocksInY = gobBlocksInY;
@@ -55,6 +58,25 @@ namespace Ryujinx.Graphics.Texture
 
                 int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
 
+                if (is3D)
+                {
+                    int gobSize = mipGobBlocksInY * GobSize;
+
+                    int sliceSize = totalBlocksOfGobsInY * widthInGobs * gobSize;
+
+                    int baseOffset = layerSize;
+
+                    int mask = gobBlocksInZ - 1;
+
+                    for (int z = 0; z < d; z++)
+                    {
+                        int zLow  = z &  mask;
+                        int zHigh = z & ~mask;
+
+                        allOffsets[z * levels + level] = baseOffset + zLow * gobSize + zHigh * sliceSize;
+                    }
+                }
+
                 mipOffsets[level] = layerSize;
 
                 layerSize += totalBlocksOfGobsInZ * totalBlocksOfGobsInY * robSize;
@@ -68,16 +90,17 @@ namespace Ryujinx.Graphics.Texture
                 gobBlocksInY,
                 gobBlocksInZ);
 
-            int[] allOffsets = new int[levels * layers];
-
-            for (int layer = 0; layer < layers; layer++)
+            if (!is3D)
             {
-                int baseIndex  = layer * levels;
-                int baseOffset = layer * layerSize;
-
-                for (int level = 0; level < levels; level++)
+                for (int layer = 0; layer < layers; layer++)
                 {
-                    allOffsets[baseIndex + level] = baseOffset + mipOffsets[level];
+                    int baseIndex  = layer * levels;
+                    int baseOffset = layer * layerSize;
+
+                    for (int level = 0; level < levels; level++)
+                    {
+                        allOffsets[baseIndex + level] = baseOffset + mipOffsets[level];
+                    }
                 }
             }