From 894cc9d876a70947aecc7a1a3f9ef869e8088f42 Mon Sep 17 00:00:00 2001
From: Feng Chen <vonchenplus@gmail.com>
Date: Wed, 17 Nov 2021 12:21:17 +0800
Subject: [PATCH] Fix image update/download error when width too small

---
 .../renderer_opengl/gl_texture_cache.cpp      | 27 ++++++++++++-------
 .../renderer_opengl/gl_texture_cache.h        |  1 +
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 2f7d98d8bf..5cfb6bb8ab 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -317,13 +317,12 @@ void AttachTexture(GLuint fbo, GLenum attachment, const ImageView* image_view) {
     }
 }
 
-OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_format) {
+OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_format,
+                     GLsizei gl_num_levels) {
     const GLenum target = ImageTarget(info);
     const GLsizei width = info.size.width;
     const GLsizei height = info.size.height;
     const GLsizei depth = info.size.depth;
-    const int max_host_mip_levels = std::bit_width(info.size.width);
-    const GLsizei num_levels = std::min(info.resources.levels, max_host_mip_levels);
     const GLsizei num_layers = info.resources.layers;
     const GLsizei num_samples = info.num_samples;
 
@@ -335,10 +334,10 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
     }
     switch (target) {
     case GL_TEXTURE_1D_ARRAY:
-        glTextureStorage2D(handle, num_levels, gl_internal_format, width, num_layers);
+        glTextureStorage2D(handle, gl_num_levels, gl_internal_format, width, num_layers);
         break;
     case GL_TEXTURE_2D_ARRAY:
-        glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, num_layers);
+        glTextureStorage3D(handle, gl_num_levels, gl_internal_format, width, height, num_layers);
         break;
     case GL_TEXTURE_2D_MULTISAMPLE_ARRAY: {
         // TODO: Where should 'fixedsamplelocations' come from?
@@ -348,10 +347,10 @@ OGLTexture MakeImage(const VideoCommon::ImageInfo& info, GLenum gl_internal_form
         break;
     }
     case GL_TEXTURE_RECTANGLE:
-        glTextureStorage2D(handle, num_levels, gl_internal_format, width, height);
+        glTextureStorage2D(handle, gl_num_levels, gl_internal_format, width, height);
         break;
     case GL_TEXTURE_3D:
-        glTextureStorage3D(handle, num_levels, gl_internal_format, width, height, depth);
+        glTextureStorage3D(handle, gl_num_levels, gl_internal_format, width, height, depth);
         break;
     case GL_TEXTURE_BUFFER:
         UNREACHABLE();
@@ -686,7 +685,9 @@ Image::Image(TextureCacheRuntime& runtime_, const VideoCommon::ImageInfo& info_,
         gl_format = tuple.format;
         gl_type = tuple.type;
     }
-    texture = MakeImage(info, gl_internal_format);
+    const int max_host_mip_levels = std::bit_width(info.size.width);
+    gl_num_levels = std::min(info.resources.levels, max_host_mip_levels);
+    texture = MakeImage(info, gl_internal_format, gl_num_levels);
     current_texture = texture.handle;
     if (runtime->device.HasDebuggingToolAttached()) {
         const std::string name = VideoCommon::Name(*this);
@@ -714,6 +715,9 @@ void Image::UploadMemory(const ImageBufferMap& map,
     u32 current_image_height = std::numeric_limits<u32>::max();
 
     for (const VideoCommon::BufferImageCopy& copy : copies) {
+        if (copy.image_subresource.base_level >= gl_num_levels) {
+            continue;
+        }
         if (current_row_length != copy.buffer_row_length) {
             current_row_length = copy.buffer_row_length;
             glPixelStorei(GL_UNPACK_ROW_LENGTH, current_row_length);
@@ -743,6 +747,9 @@ void Image::DownloadMemory(ImageBufferMap& map,
     u32 current_image_height = std::numeric_limits<u32>::max();
 
     for (const VideoCommon::BufferImageCopy& copy : copies) {
+        if (copy.image_subresource.base_level >= gl_num_levels) {
+            continue;
+        }
         if (current_row_length != copy.buffer_row_length) {
             current_row_length = copy.buffer_row_length;
             glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
@@ -782,7 +789,7 @@ GLuint Image::StorageHandle() noexcept {
         }
         store_view.Create();
         glTextureView(store_view.handle, ImageTarget(info), current_texture, GL_RGBA8, 0,
-                      info.resources.levels, 0, info.resources.layers);
+                      gl_num_levels, 0, info.resources.layers);
         return store_view.handle;
     default:
         return current_texture;
@@ -946,7 +953,7 @@ void Image::Scale(bool up_scale) {
         auto dst_info = info;
         dst_info.size.width = scaled_width;
         dst_info.size.height = scaled_height;
-        upscaled_backup = MakeImage(dst_info, gl_internal_format);
+        upscaled_backup = MakeImage(dst_info, gl_internal_format, gl_num_levels);
     }
     const u32 src_width = up_scale ? original_width : scaled_width;
     const u32 src_height = up_scale ? original_height : scaled_height;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 1bb7625685..30037a6a29 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -219,6 +219,7 @@ private:
     GLenum gl_internal_format = GL_NONE;
     GLenum gl_format = GL_NONE;
     GLenum gl_type = GL_NONE;
+    GLsizei gl_num_levels{};
     TextureCacheRuntime* runtime{};
     GLuint current_texture{};
 };