From ca78d3493347285e67457f6fb9a064e70530d332 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Mon, 2 Jul 2018 12:55:04 +0300
Subject: [PATCH 1/7] gl_rasterizer: implement mipmap

---
 src/video_core/regs_texturing.h               |   7 +-
 .../renderer_opengl/gl_rasterizer.cpp         |  32 ++++-
 .../renderer_opengl/gl_rasterizer.h           |   4 +
 .../renderer_opengl/gl_rasterizer_cache.cpp   | 109 +++++++++++++++---
 .../renderer_opengl/gl_rasterizer_cache.h     |   7 +-
 src/video_core/renderer_opengl/pica_to_gl.h   |  51 ++++----
 6 files changed, 163 insertions(+), 47 deletions(-)

diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h
index d07aa28b7..3954e13b4 100644
--- a/src/video_core/regs_texturing.h
+++ b/src/video_core/regs_texturing.h
@@ -59,11 +59,16 @@ struct TexturingRegs {
             BitField<2, 1, TextureFilter> min_filter;
             BitField<8, 3, WrapMode> wrap_t;
             BitField<12, 3, WrapMode> wrap_s;
+            BitField<24, 1, TextureFilter> mip_filter;
             /// @note Only valid for texture 0 according to 3DBrew.
             BitField<28, 3, TextureType> type;
         };
 
-        INSERT_PADDING_WORDS(0x1);
+        union {
+            BitField<0, 13, s32> bias; // fixed1.4.8
+            BitField<16, 4, u32> max_level;
+            BitField<24, 4, u32> min_level;
+        } lod;
 
         BitField<0, 28, u32> address;
 
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 923f2efd3..475d2a88b 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1555,13 +1555,16 @@ bool RasterizerOpenGL::AccelerateDisplay(const GPU::Regs::FramebufferConfig& con
 
 void RasterizerOpenGL::SamplerInfo::Create() {
     sampler.Create();
-    mag_filter = min_filter = TextureConfig::Linear;
+    mag_filter = min_filter = mip_filter = TextureConfig::Linear;
     wrap_s = wrap_t = TextureConfig::Repeat;
     border_color = 0;
+    lod_min = lod_max = 0;
+    lod_bias = 0;
 
-    // default is GL_LINEAR_MIPMAP_LINEAR
-    glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    // default is 1000 and -1000
     // Other attributes have correct defaults
+    glSamplerParameterf(sampler.handle, GL_TEXTURE_MAX_LOD, lod_max);
+    glSamplerParameterf(sampler.handle, GL_TEXTURE_MIN_LOD, lod_min);
 }
 
 void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
@@ -1571,11 +1574,13 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
 
     if (mag_filter != config.mag_filter) {
         mag_filter = config.mag_filter;
-        glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureFilterMode(mag_filter));
+        glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, PicaToGL::TextureMagFilterMode(mag_filter));
     }
-    if (min_filter != config.min_filter) {
+    if (min_filter != config.min_filter || mip_filter != config.mip_filter) {
         min_filter = config.min_filter;
-        glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, PicaToGL::TextureFilterMode(min_filter));
+        mip_filter = config.mip_filter;
+        glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
+                            PicaToGL::TextureMinFilterMode(min_filter, mip_filter));
     }
 
     if (wrap_s != config.wrap_s) {
@@ -1594,6 +1599,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
             glSamplerParameterfv(s, GL_TEXTURE_BORDER_COLOR, gl_color.data());
         }
     }
+
+    if (lod_min != config.lod.min_level) {
+        lod_min = config.lod.min_level;
+        glSamplerParameterf(s, GL_TEXTURE_MIN_LOD, lod_min);
+    }
+
+    if (lod_max != config.lod.max_level) {
+        lod_max = config.lod.max_level;
+        glSamplerParameterf(s, GL_TEXTURE_MAX_LOD, lod_max);
+    }
+
+    if (lod_bias != config.lod.bias) {
+        lod_bias = config.lod.bias;
+        glSamplerParameterf(s, GL_TEXTURE_LOD_BIAS, lod_bias / 256.0f);
+    }
 }
 
 void RasterizerOpenGL::SetShader() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 730364819..f890c1049 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -69,9 +69,13 @@ private:
     private:
         TextureConfig::TextureFilter mag_filter;
         TextureConfig::TextureFilter min_filter;
+        TextureConfig::TextureFilter mip_filter;
         TextureConfig::WrapMode wrap_s;
         TextureConfig::WrapMode wrap_t;
         u32 border_color;
+        u32 lod_min;
+        u32 lod_max;
+        s32 lod_bias;
     };
 
     /// Structure that the hardware rendered vertices are composed of
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 44dd2e78f..5f7ceecaa 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1326,10 +1326,11 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(
     const Pica::TexturingRegs::FullTextureConfig& config) {
     Pica::Texture::TextureInfo info =
         Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format);
-    return GetTextureSurface(info);
+    return GetTextureSurface(info, config.config.lod.max_level);
 }
 
-Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info) {
+Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info,
+                                                 u32 max_level) {
     SurfaceParams params;
     params.addr = info.physical_address;
     params.width = info.width;
@@ -1338,23 +1339,97 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
     params.pixel_format = SurfaceParams::PixelFormatFromTextureFormat(info.format);
     params.UpdateParams();
 
-    if (info.width % 8 != 0 || info.height % 8 != 0) {
-        Surface src_surface;
-        Common::Rectangle<u32> rect;
-        std::tie(src_surface, rect) = GetSurfaceSubRect(params, ScaleMatch::Ignore, true);
-
-        params.res_scale = src_surface->res_scale;
-        Surface tmp_surface = CreateSurface(params);
-        BlitTextures(src_surface->texture.handle, rect, tmp_surface->texture.handle,
-                     tmp_surface->GetScaledRect(),
-                     SurfaceParams::GetFormatType(params.pixel_format), read_framebuffer.handle,
-                     draw_framebuffer.handle);
-
-        remove_surfaces.emplace(tmp_surface);
-        return tmp_surface;
+    u32 min_width = info.width >> max_level;
+    u32 min_height = info.height >> max_level;
+    if (min_width % 8 != 0 || min_height % 8 != 0) {
+        LOG_CRITICAL(Render_OpenGL, "Texture size ({}x{}) is not multiple of 8", min_width,
+                     min_height);
+        return nullptr;
+    }
+    if (info.width != (min_width << max_level) || info.height != (min_height << max_level)) {
+        LOG_CRITICAL(Render_OpenGL,
+                     "Texture size ({}x{}) does not support required mipmap level ({})",
+                     params.width, params.height, max_level);
+        return nullptr;
     }
 
-    return GetSurface(params, ScaleMatch::Ignore, true);
+    auto surface = GetSurface(params, ScaleMatch::Ignore, true);
+
+    // Update mipmap if necessary
+    if (max_level != 0) {
+        if (max_level >= 8) {
+            // since PICA only supports texture size between 8 and 1024, there are at most eight
+            // possible mipmap levels including the base.
+            LOG_CRITICAL(Render_OpenGL, "Unsupported mipmap level {}", max_level);
+            return nullptr;
+        }
+        OpenGLState prev_state = OpenGLState::GetCurState();
+        OpenGLState state;
+        SCOPE_EXIT({ prev_state.Apply(); });
+        auto format_tuple = GetFormatTuple(params.pixel_format);
+
+        // Allocate more mipmap level if necessary
+        if (surface->max_level < max_level) {
+            state.texture_units[0].texture_2d = surface->texture.handle;
+            state.Apply();
+            glActiveTexture(GL_TEXTURE0);
+            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, max_level);
+            u32 width = surface->width * surface->res_scale;
+            u32 height = surface->height * surface->res_scale;
+            for (u32 level = surface->max_level + 1; level <= max_level; ++level) {
+                glTexImage2D(GL_TEXTURE_2D, level, format_tuple.internal_format, width >> level,
+                             height >> level, 0, format_tuple.format, format_tuple.type, nullptr);
+            }
+            surface->max_level = max_level;
+        }
+
+        // Blit mipmaps that have been invalidated
+        state.draw.read_framebuffer = read_framebuffer.handle;
+        state.draw.draw_framebuffer = draw_framebuffer.handle;
+        state.ResetTexture(surface->texture.handle);
+        SurfaceParams params = *surface;
+        for (u32 level = 1; level <= max_level; ++level) {
+            // In PICA all mipmap levels are stored next to each other
+            params.addr += params.width * params.height * params.GetFormatBpp() / 8;
+            params.width /= 2;
+            params.height /= 2;
+            params.stride = 0; // reset stride and let UpdateParams re-initialize it
+            params.UpdateParams();
+            auto& watcher = surface->level_watchers[level - 1];
+            if (!watcher || !watcher->Get()) {
+                auto level_surface = GetSurface(params, ScaleMatch::Ignore, true);
+                if (level_surface) {
+                    watcher = level_surface->CreateWatcher();
+                } else {
+                    watcher = nullptr;
+                }
+            }
+
+            if (watcher && !watcher->IsValid()) {
+                auto level_surface = watcher->Get();
+                state.ResetTexture(level_surface->texture.handle);
+                state.Apply();
+                glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                                       level_surface->texture.handle, 0);
+                glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+                                       GL_TEXTURE_2D, 0, 0);
+
+                glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+                                       surface->texture.handle, level);
+                glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
+                                       GL_TEXTURE_2D, 0, 0);
+
+                auto src_rect = level_surface->GetScaledRect();
+                auto dst_rect = params.GetScaledRect();
+                glBlitFramebuffer(src_rect.left, src_rect.bottom, src_rect.right, src_rect.top,
+                                  dst_rect.left, dst_rect.bottom, dst_rect.right, dst_rect.top,
+                                  GL_COLOR_BUFFER_BIT, GL_LINEAR);
+                watcher->Validate();
+            }
+        }
+    }
+
+    return surface;
 }
 
 const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCubeConfig& config) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 555e9078b..f3dbdd8dd 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -356,6 +356,11 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface
 
     OGLTexture texture;
 
+    /// max mipmap level that has been attached to the texture
+    u32 max_level = 0;
+    /// level_watchers[i] watches the (i+1)-th level mipmap source surface
+    std::array<std::shared_ptr<SurfaceWatcher>, 7> level_watchers;
+
     static constexpr unsigned int GetGLBytesPerPixel(PixelFormat format) {
         // OpenGL needs 4 bpp alignment for D24 since using GL_UNSIGNED_INT as type
         return format == PixelFormat::Invalid
@@ -434,7 +439,7 @@ public:
 
     /// Get a surface based on the texture configuration
     Surface GetTextureSurface(const Pica::TexturingRegs::FullTextureConfig& config);
-    Surface GetTextureSurface(const Pica::Texture::TextureInfo& info);
+    Surface GetTextureSurface(const Pica::Texture::TextureInfo& info, u32 max_level = 0);
 
     /// Get a texture cube based on the texture configuration
     const CachedTextureCube& GetTextureCube(const TextureCubeConfig& config);
diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h
index 0bdc63d4a..c7cda0ba9 100644
--- a/src/video_core/renderer_opengl/pica_to_gl.h
+++ b/src/video_core/renderer_opengl/pica_to_gl.h
@@ -29,33 +29,40 @@ using GLivec4 = std::array<GLint, 4>;
 
 namespace PicaToGL {
 
-inline GLenum TextureFilterMode(Pica::TexturingRegs::TextureConfig::TextureFilter mode) {
-    static constexpr std::array<GLenum, 2> filter_mode_table{{
-        GL_NEAREST, // TextureFilter::Nearest
-        GL_LINEAR,  // TextureFilter::Linear
-    }};
-
-    const auto index = static_cast<std::size_t>(mode);
-
-    // Range check table for input
-    if (index >= filter_mode_table.size()) {
-        LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", index);
-        UNREACHABLE();
+using TextureFilter = Pica::TexturingRegs::TextureConfig::TextureFilter;
 
+inline GLenum TextureMagFilterMode(TextureFilter mode) {
+    if (mode == TextureFilter::Linear) {
         return GL_LINEAR;
     }
-
-    GLenum gl_mode = filter_mode_table[index];
-
-    // Check for dummy values indicating an unknown mode
-    if (gl_mode == 0) {
-        LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", index);
-        UNIMPLEMENTED();
-
-        return GL_LINEAR;
+    if (mode == TextureFilter::Nearest) {
+        return GL_NEAREST;
     }
+    LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {}", static_cast<u32>(mode));
+    UNIMPLEMENTED();
+    return GL_LINEAR;
+}
 
-    return gl_mode;
+inline GLenum TextureMinFilterMode(TextureFilter min, TextureFilter mip) {
+    if (min == TextureFilter::Linear) {
+        if (mip == TextureFilter::Linear) {
+            return GL_LINEAR_MIPMAP_LINEAR;
+        }
+        if (mip == TextureFilter::Nearest) {
+            return GL_LINEAR_MIPMAP_NEAREST;
+        }
+    } else if (min == TextureFilter::Nearest) {
+        if (mip == TextureFilter::Linear) {
+            return GL_NEAREST_MIPMAP_LINEAR;
+        }
+        if (mip == TextureFilter::Nearest) {
+            return GL_NEAREST_MIPMAP_NEAREST;
+        }
+    }
+    LOG_CRITICAL(Render_OpenGL, "Unknown texture filtering mode {} and {}", static_cast<u32>(min),
+                 static_cast<u32>(mip));
+    UNIMPLEMENTED();
+    return GL_LINEAR_MIPMAP_LINEAR;
 }
 
 inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {

From fa141c799b47adaae8e34196cdf8e3550513ac4b Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Fri, 6 Jul 2018 00:59:43 +0300
Subject: [PATCH 2/7] gl_shader_gen: use accurate LOD formula for texture 2D

---
 .../renderer_opengl/gl_shader_gen.cpp          | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 3ea883038..1e3b9f9c8 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -317,8 +317,9 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
         // Only unit 0 respects the texturing type
         switch (state.texture0_type) {
         case TexturingRegs::TextureConfig::Texture2D:
-            return "texture(tex0, texcoord0)";
+            return "textureLod(tex0, texcoord0, getLod(texcoord0 * textureSize(tex0, 0)))";
         case TexturingRegs::TextureConfig::Projection2D:
+            // TODO (wwylele): find the exact LOD formula for projection texture
             return "textureProj(tex0, vec3(texcoord0, texcoord0_w))";
         case TexturingRegs::TextureConfig::TextureCube:
             return "texture(tex_cube, vec3(texcoord0, texcoord0_w))";
@@ -335,12 +336,12 @@ static std::string SampleTexture(const PicaFSConfig& config, unsigned texture_un
             return "texture(tex0, texcoord0)";
         }
     case 1:
-        return "texture(tex1, texcoord1)";
+        return "textureLod(tex1, texcoord1, getLod(texcoord1 * textureSize(tex1, 0)))";
     case 2:
         if (state.texture2_use_coord1)
-            return "texture(tex2, texcoord1)";
+            return "textureLod(tex2, texcoord1, getLod(texcoord1 * textureSize(tex2, 0)))";
         else
-            return "texture(tex2, texcoord2)";
+            return "textureLod(tex2, texcoord2, getLod(texcoord2 * textureSize(tex2, 0)))";
     case 3:
         if (state.proctex.enable) {
             return "ProcTex()";
@@ -1333,6 +1334,15 @@ vec4 byteround(vec4 x) {
     return round(x * 255.0) * (1.0 / 255.0);
 }
 
+// PICA's LOD formula for 2D textures.
+// This LOD formula is the same as the LOD lower limit defined in OpenGL.
+// f(x, y) >= max{m_u, m_v, m_w}
+// (See OpenGL 4.6 spec, 8.14.1 - Scale Factor and Level-of-Detail)
+float getLod(vec2 coord) {
+    vec2 d = max(abs(dFdx(coord)), abs(dFdy(coord)));
+    return log2(max(d.x, d.y));
+}
+
 #if ALLOW_SHADOW
 
 uvec2 DecodeShadow(uint pixel) {

From d7196b5573c625ca24b75e617f15d76f0f62c94e Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Mon, 9 Jul 2018 21:19:14 +0300
Subject: [PATCH 3/7] gl_rasterizer_cache: invalidate watchers on (partial)
 surface invalidation

---
 src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 5f7ceecaa..932a8ad9d 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1807,6 +1807,7 @@ void RasterizerCacheOpenGL::InvalidateRegion(PAddr addr, u32 size, const Surface
 
             const auto interval = cached_surface->GetInterval() & invalid_interval;
             cached_surface->invalid_regions.insert(interval);
+            cached_surface->InvalidateAllWatcher();
 
             // Remove only "empty" fill surfaces to avoid destroying and recreating OGL textures
             if (cached_surface->type == SurfaceType::Fill &&

From 777af04f4ae1e08f544d50b6f45f40f25c9f61b3 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Sun, 22 Jul 2018 17:31:02 +0300
Subject: [PATCH 4/7] gl_rasterizer: ignore mipmap setting for cubemap before
 we implements it

---
 src/video_core/renderer_opengl/gl_rasterizer.cpp | 15 +++++++++++++++
 src/video_core/renderer_opengl/gl_rasterizer.h   |  3 +++
 2 files changed, 18 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 475d2a88b..8730d1fe9 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -1583,6 +1583,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(
                             PicaToGL::TextureMinFilterMode(min_filter, mip_filter));
     }
 
+    // TODO(wwylele): remove this block once mipmap for cube is implemented
+    bool new_supress_mipmap_for_cube =
+        config.type == Pica::TexturingRegs::TextureConfig::TextureCube;
+    if (supress_mipmap_for_cube != new_supress_mipmap_for_cube) {
+        supress_mipmap_for_cube = new_supress_mipmap_for_cube;
+        if (new_supress_mipmap_for_cube) {
+            // HACK: use mag filter converter for min filter because they are the same anyway
+            glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
+                                PicaToGL::TextureMagFilterMode(min_filter));
+        } else {
+            glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
+                                PicaToGL::TextureMinFilterMode(min_filter, mip_filter));
+        }
+    }
+
     if (wrap_s != config.wrap_s) {
         wrap_s = config.wrap_s;
         glSamplerParameteri(s, GL_TEXTURE_WRAP_S, PicaToGL::WrapMode(wrap_s));
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index f890c1049..c225d62b7 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -76,6 +76,9 @@ private:
         u32 lod_min;
         u32 lod_max;
         s32 lod_bias;
+
+        // TODO(wwylele): remove this once mipmap for cube is implemented
+        bool supress_mipmap_for_cube = false;
     };
 
     /// Structure that the hardware rendered vertices are composed of

From ebdef4fd6949a6e1e070575aa0ef73ac8164a076 Mon Sep 17 00:00:00 2001
From: wwylele <wwylele@gmail.com>
Date: Tue, 24 Jul 2018 23:18:36 +0300
Subject: [PATCH 5/7] gl_rasterizer_cache: unlink watchers if surface is moved
 to remove_surfaces but is not immediately removed

---
 src/video_core/renderer_opengl/gl_rasterizer_cache.cpp |  1 +
 src/video_core/renderer_opengl/gl_rasterizer_cache.h   | 10 ++++++++++
 2 files changed, 11 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 932a8ad9d..66fbf2322 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1300,6 +1300,7 @@ SurfaceRect_Tuple RasterizerCacheOpenGL::GetSurfaceSubRect(const SurfaceParams&
 
             // Delete the expanded surface, this can't be done safely yet
             // because it may still be in use
+            surface->UnlinkAllWatcher(); // unlink watchers as if this surface is already deleted
             remove_surfaces.emplace(surface);
 
             surface = new_surface;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index f3dbdd8dd..962cbceb6 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -397,6 +397,16 @@ struct CachedSurface : SurfaceParams, std::enable_shared_from_this<CachedSurface
         }
     }
 
+    void UnlinkAllWatcher() {
+        for (const auto& watcher : watchers) {
+            if (auto locked = watcher.lock()) {
+                locked->valid = false;
+                locked->surface.reset();
+            }
+        }
+        watchers.clear();
+    }
+
 private:
     std::list<std::weak_ptr<SurfaceWatcher>> watchers;
 };

From 88a011ec8efba5634a827988272dcb67bb639cfa Mon Sep 17 00:00:00 2001
From: Weiyi Wang <wwylele@gmail.com>
Date: Sun, 10 Mar 2019 11:02:56 -0400
Subject: [PATCH 6/7] GetTextureSurface: return on invalid physical address
 early

Previously this check is in GetSurface (if (addr == 0)). This worked fine because GetTextureSurface directly forwarded the address value to GetSurface. However, now with mipmap support, GetTextureSurface would call GetSurface several times with different address offset, resulting some >0 but still invalid address in case the input is 0. We should error out early on invalid address instead of sending it furthor down which would cause invalid memory access
---
 src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 66fbf2322..13e318e04 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1332,6 +1332,10 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(
 
 Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInfo& info,
                                                  u32 max_level) {
+    if (info.physical_address == 0) {
+        return nullptr;
+    }
+
     SurfaceParams params;
     params.addr = info.physical_address;
     params.width = info.width;

From e3b6bf93bc506181afee8810f7897b0d4aadbf52 Mon Sep 17 00:00:00 2001
From: Weiyi Wang <wwylele@gmail.com>
Date: Mon, 15 Apr 2019 09:07:36 -0400
Subject: [PATCH 7/7] gl_rasterizer_cache: validate surface in mipmap/cubemap
 if the children is not validated yet

---
 src/video_core/renderer_opengl/gl_rasterizer_cache.cpp | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 13e318e04..292c50f7c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -1412,6 +1412,9 @@ Surface RasterizerCacheOpenGL::GetTextureSurface(const Pica::Texture::TextureInf
 
             if (watcher && !watcher->IsValid()) {
                 auto level_surface = watcher->Get();
+                if (!level_surface->invalid_regions.empty()) {
+                    ValidateSurface(level_surface, level_surface->addr, level_surface->size);
+                }
                 state.ResetTexture(level_surface->texture.handle);
                 state.Apply();
                 glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
@@ -1504,6 +1507,9 @@ const CachedTextureCube& RasterizerCacheOpenGL::GetTextureCube(const TextureCube
     for (const Face& face : faces) {
         if (face.watcher && !face.watcher->IsValid()) {
             auto surface = face.watcher->Get();
+            if (!surface->invalid_regions.empty()) {
+                ValidateSurface(surface, surface->addr, surface->size);
+            }
             state.ResetTexture(surface->texture.handle);
             state.Apply();
             glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,