From 5b37cecd76205612bfc2cc1d0b475d893fe7ee6a Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 29 May 2020 01:48:01 -0300
Subject: [PATCH 1/4] texture_cache: Handle overlaps with multiple subresources

Implement more surface reconstruct cases. Allow overlaps with more than
one layer and mipmap and copies all of them to the new texture.

- Fixes textures moving around objects on Xenoblade games
---
 src/video_core/texture_cache/texture_cache.h | 62 +++++++++++---------
 1 file changed, 34 insertions(+), 28 deletions(-)

diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 8bfc541d4..658264860 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -652,45 +652,54 @@ private:
      **/
     std::optional<std::pair<TSurface, TView>> TryReconstructSurface(std::vector<TSurface>& overlaps,
                                                                     const SurfaceParams& params,
-                                                                    const GPUVAddr gpu_addr) {
+                                                                    GPUVAddr gpu_addr) {
         if (params.target == SurfaceTarget::Texture3D) {
-            return {};
+            return std::nullopt;
         }
-        bool modified = false;
         TSurface new_surface = GetUncachedSurface(gpu_addr, params);
-        u32 passed_tests = 0;
+        std::size_t passed_tests = 0;
+        bool modified = false;
+
         for (auto& surface : overlaps) {
             const SurfaceParams& src_params = surface->GetSurfaceParams();
-            if (src_params.is_layered || src_params.num_levels > 1) {
-                // We send this cases to recycle as they are more complex to handle
-                return {};
-            }
-            const std::size_t candidate_size = surface->GetSizeInBytes();
-            auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
+            const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
             if (!mipmap_layer) {
                 continue;
             }
-            const auto [layer, mipmap] = *mipmap_layer;
-            if (new_surface->GetMipmapSize(mipmap) != candidate_size) {
+            const auto [base_layer, base_mipmap] = *mipmap_layer;
+            if (new_surface->GetMipmapSize(base_mipmap) != surface->GetMipmapSize(0)) {
                 continue;
             }
+
+            // Copy all mipmaps and layers
+            const u32 block_width = params.GetDefaultBlockWidth();
+            const u32 block_height = params.GetDefaultBlockHeight();
+            for (u32 mipmap = base_mipmap; mipmap < base_mipmap + src_params.num_levels; ++mipmap) {
+                const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap);
+                const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap);
+                if (width < block_width || height < block_height) {
+                    // Current APIs forbid copying small compressed textures, avoid errors
+                    break;
+                }
+                const CopyParams copy_params(0, 0, 0, 0, 0, base_layer, 0, mipmap, width, height,
+                                             src_params.depth);
+                ImageCopy(surface, new_surface, copy_params);
+            }
+            ++passed_tests;
             modified |= surface->IsModified();
-            // Now we got all the data set up
-            const u32 width = SurfaceParams::IntersectWidth(src_params, params, 0, mipmap);
-            const u32 height = SurfaceParams::IntersectHeight(src_params, params, 0, mipmap);
-            const CopyParams copy_params(0, 0, 0, 0, 0, layer, 0, mipmap, width, height, 1);
-            passed_tests++;
-            ImageCopy(surface, new_surface, copy_params);
         }
         if (passed_tests == 0) {
-            return {};
-            // In Accurate GPU all tests should pass, else we recycle
-        } else if (Settings::IsGPULevelExtreme() && passed_tests != overlaps.size()) {
-            return {};
+            return std::nullopt;
         }
+        if (Settings::IsGPULevelExtreme() && passed_tests != overlaps.size()) {
+            // In Accurate GPU all tests should pass, else we recycle
+            return std::nullopt;
+        }
+
         for (const auto& surface : overlaps) {
             Unregister(surface);
         }
+
         new_surface->MarkAsModified(modified, Tick());
         Register(new_surface);
         return {{new_surface, new_surface->GetMainView()}};
@@ -868,12 +877,9 @@ private:
             // two things either the candidate surface is a supertexture of the overlap
             // or they don't match in any known way.
             if (!current_surface->IsInside(gpu_addr, gpu_addr + candidate_size)) {
-                if (current_surface->GetGpuAddr() == gpu_addr) {
-                    std::optional<std::pair<TSurface, TView>> view =
-                        TryReconstructSurface(overlaps, params, gpu_addr);
-                    if (view) {
-                        return *view;
-                    }
+                const std::optional view = TryReconstructSurface(overlaps, params, gpu_addr);
+                if (view) {
+                    return *view;
                 }
                 return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
                                       MatchTopologyResult::FullMatch);

From dd70e097ccb84b64983456759525d650d1ceab0a Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 29 May 2020 20:10:58 -0300
Subject: [PATCH 2/4] texture_cache: Reload textures when number of resources
 mismatch

---
 src/video_core/texture_cache/texture_cache.h | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 658264860..62206b906 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -660,6 +660,15 @@ private:
         std::size_t passed_tests = 0;
         bool modified = false;
 
+        u32 num_resources = 0;
+        for (auto& surface : overlaps) {
+            const SurfaceParams& src_params = surface->GetSurfaceParams();
+            num_resources += src_params.depth * src_params.num_levels;
+        }
+        if (num_resources != params.depth * params.num_levels) {
+            LoadSurface(new_surface);
+        }
+
         for (auto& surface : overlaps) {
             const SurfaceParams& src_params = surface->GetSurfaceParams();
             const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};

From e454f7e7a7d75fe0415ce48ffbd5d5979d79ce67 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 29 May 2020 20:12:46 -0300
Subject: [PATCH 3/4] texture_cache: Only copy textures that were modified from
 host

---
 src/video_core/texture_cache/texture_cache.h | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 62206b906..3e024a098 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -679,6 +679,12 @@ private:
             if (new_surface->GetMipmapSize(base_mipmap) != surface->GetMipmapSize(0)) {
                 continue;
             }
+            ++passed_tests;
+
+            if (!surface->IsModified()) {
+                continue;
+            }
+            modified = true;
 
             // Copy all mipmaps and layers
             const u32 block_width = params.GetDefaultBlockWidth();
@@ -694,8 +700,6 @@ private:
                                              src_params.depth);
                 ImageCopy(surface, new_surface, copy_params);
             }
-            ++passed_tests;
-            modified |= surface->IsModified();
         }
         if (passed_tests == 0) {
             return std::nullopt;

From 1ee1a5d3d64379bf2463c072a32af8e64a8c14cf Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 29 May 2020 23:52:47 -0300
Subject: [PATCH 4/4] texture_cache: More relaxed reconstruction

Only reupload textures when they've not been modified from the GPU.
---
 src/video_core/texture_cache/texture_cache.h | 22 ++++++++------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 3e024a098..4ba0d2c3a 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -656,19 +656,19 @@ private:
         if (params.target == SurfaceTarget::Texture3D) {
             return std::nullopt;
         }
+        const auto test_modified = [](TSurface& surface) { return surface->IsModified(); };
         TSurface new_surface = GetUncachedSurface(gpu_addr, params);
-        std::size_t passed_tests = 0;
-        bool modified = false;
 
-        u32 num_resources = 0;
-        for (auto& surface : overlaps) {
-            const SurfaceParams& src_params = surface->GetSurfaceParams();
-            num_resources += src_params.depth * src_params.num_levels;
-        }
-        if (num_resources != params.depth * params.num_levels) {
+        if (std::none_of(overlaps.begin(), overlaps.end(), test_modified)) {
             LoadSurface(new_surface);
+            for (const auto& surface : overlaps) {
+                Unregister(surface);
+            }
+            Register(new_surface);
+            return {{new_surface, new_surface->GetMainView()}};
         }
 
+        std::size_t passed_tests = 0;
         for (auto& surface : overlaps) {
             const SurfaceParams& src_params = surface->GetSurfaceParams();
             const auto mipmap_layer{new_surface->GetLayerMipmap(surface->GetGpuAddr())};
@@ -681,11 +681,6 @@ private:
             }
             ++passed_tests;
 
-            if (!surface->IsModified()) {
-                continue;
-            }
-            modified = true;
-
             // Copy all mipmaps and layers
             const u32 block_width = params.GetDefaultBlockWidth();
             const u32 block_height = params.GetDefaultBlockHeight();
@@ -709,6 +704,7 @@ private:
             return std::nullopt;
         }
 
+        const bool modified = std::any_of(overlaps.begin(), overlaps.end(), test_modified);
         for (const auto& surface : overlaps) {
             Unregister(surface);
         }