From 893200161020f251dd327a5ac42c55509414dc3b Mon Sep 17 00:00:00 2001
From: Fernando Sahmkow <fsahmkow27@gmail.com>
Date: Fri, 8 Feb 2019 19:51:31 -0400
Subject: [PATCH] rasterizer_cache_gl: Implement Partial Reinterpretation of
 Surfaces.

---
 .../renderer_opengl/gl_rasterizer_cache.cpp   | 89 +++++++++++++++++++
 .../renderer_opengl/gl_rasterizer_cache.h     | 11 +++
 2 files changed, 100 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index fe81ff227..367dbd041 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1304,9 +1304,98 @@ Surface RasterizerCacheOpenGL::TryGetReservedSurface(const SurfaceParams& params
     return {};
 }
 
+bool FindBestMipMap(std::size_t memory, const SurfaceParams params, u32 height, u32& mipmap) {
+    for (u32 i = 0; i < params.max_mip_level; i++)
+        if (memory == params.GetMipmapSingleSize(i) && params.MipHeight(i) == height) {
+            mipmap = i;
+            return true;
+        }
+    return false;
+}
+
+bool FindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap, u32& layer) {
+    std::size_t size = params.LayerMemorySize();
+    VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap);
+    for (u32 i = 0; i < params.depth; i++) {
+        if (start == addr) {
+            layer = i;
+            return true;
+        }
+        start += size;
+    }
+    return false;
+}
+
+bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surface render_surface,
+                                const Surface blitted_surface) {
+    const auto dst_params = blitted_surface->GetSurfaceParams();
+    const auto src_params = render_surface->GetSurfaceParams();
+    u32 level = 0;
+    std::size_t src_memory_size = src_params.size_in_bytes;
+    if (FindBestMipMap(src_memory_size, dst_params, src_params.height, level)) {
+        if (src_params.width == dst_params.MipWidthGobAligned(level) &&
+            src_params.height == dst_params.MipHeight(level) &&
+            src_params.block_height >= dst_params.MipBlockHeight(level)) {
+            u32 slot = 0;
+            if (FindBestLayer(render_surface->GetAddr(), dst_params, level, slot)) {
+                glCopyImageSubData(
+                    render_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0,
+                    0, blitted_surface->Texture().handle, SurfaceTargetToGL(dst_params.target),
+                    level, 0, 0, slot, dst_params.MipWidth(level), dst_params.MipHeight(level), 1);
+                blitted_surface->MarkAsModified(true, cache);
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) {
+    VAddr bound1 = blitted_surface->GetAddr() + blitted_surface->GetMemorySize();
+    VAddr bound2 = render_surface->GetAddr() + render_surface->GetMemorySize();
+    if (bound2 > bound1)
+        return true;
+    const auto dst_params = blitted_surface->GetSurfaceParams();
+    const auto src_params = render_surface->GetSurfaceParams();
+    if (dst_params.component_type != src_params.component_type)
+        return true;
+    return false;
+}
+
+bool IsReinterpretInvalidSecond(const Surface render_surface, const Surface blitted_surface) {
+    const auto dst_params = blitted_surface->GetSurfaceParams();
+    const auto src_params = render_surface->GetSurfaceParams();
+    if (dst_params.height > src_params.height && dst_params.width > src_params.width)
+        return false;
+    return true;
+}
+
+bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface,
+                                                      Surface intersect) {
+    if (IsReinterpretInvalid(triggering_surface, intersect)) {
+        Unregister(intersect);
+        return false;
+    }
+    if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) {
+        if (IsReinterpretInvalidSecond(triggering_surface, intersect)) {
+            Unregister(intersect);
+            return false;
+        }
+        FlushObject(intersect);
+        FlushObject(triggering_surface);
+        intersect->MarkForReload(true);
+    }
+    return true;
+}
+
+
 void RasterizerCacheOpenGL::NotifyFrameBufferChange(Surface triggering_surface) {
     if (triggering_surface == nullptr)
         return;
+    Surface intersect = CollideOnReinterpretedSurface(triggering_surface->GetAddr());
+    if (intersect != nullptr) {
+        PartialReinterpretSurface(triggering_surface, intersect);
+    }
 }
 
 } // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 0b4d720e2..2be056f0a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -141,10 +141,18 @@ struct SurfaceParams {
         return offset;
     }
 
+    std::size_t GetMipmapSingleSize(u32 mip_level) const {
+        return InnerMipmapMemorySize(mip_level, false, is_layered);
+    }
+
     u32 MipWidth(u32 mip_level) const {
         return std::max(1U, width >> mip_level);
     }
 
+    u32 MipWidthGobAligned(u32 mip_level) const {
+        return std::max(64U*8U / GetFormatBpp(), width >> mip_level);
+    }
+
     u32 MipHeight(u32 mip_level) const {
         return std::max(1U, height >> mip_level);
     }
@@ -480,6 +488,9 @@ private:
     /// When a render target is changed, this method is called with the previous render target
     void NotifyFrameBufferChange(Surface triggering_surface);
 
+    // Partialy reinterpret a surface based on a triggering_surface that collides with it.
+    bool PartialReinterpretSurface(Surface triggering_surface, Surface intersect);
+
     /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
     void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
     void FastLayeredCopySurface(const Surface& src_surface, const Surface& dst_surface);