From f3ff8bdc0e8c6c25c1725b82d6862b93a8df3c84 Mon Sep 17 00:00:00 2001
From: FernandoS27 <fsahmkow27@gmail.com>
Date: Tue, 19 Oct 2021 17:46:01 +0200
Subject: [PATCH] TextureCache: Fix blitting filter in Vulkan and correct
 viewport/scissor calculation when downscaling.

---
 .../renderer_vulkan/vk_rasterizer.cpp         | 24 ++++++++---
 .../renderer_vulkan/vk_texture_cache.cpp      | 40 ++++++++++++-------
 2 files changed, 44 insertions(+), 20 deletions(-)

diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 1ceffa7188..a9334e1018 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -60,10 +60,19 @@ struct DrawParams {
 
 VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) {
     const auto& src = regs.viewport_transform[index];
-    const float x = (src.translate_x - src.scale_x) * scale;
-    const float width = src.scale_x * 2.0f * scale;
-    float y = (src.translate_y - src.scale_y) * scale;
-    float height = src.scale_y * 2.0f * scale;
+    const auto conv = [scale](float value) {
+        float new_value = value * scale;
+        if (scale < 1.0f) {
+            bool sign = std::signbit(new_value);
+            new_value = std::round(std::abs(new_value));
+            new_value = sign ? -new_value : new_value;
+        }
+        return new_value;
+    };
+    const float x = conv(src.translate_x - src.scale_x);
+    const float width = conv(src.scale_x * 2.0f);
+    float y = conv(src.translate_y - src.scale_y);
+    float height = conv(src.scale_y * 2.0f);
     if (regs.screen_y_control.y_negate) {
         y += height;
         height = -height;
@@ -91,8 +100,13 @@ VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u3
         if (value == 0) {
             return 0U;
         }
+        const u32 upset = value * up_scale;
+        u32 acumm = 0;
+        if ((up_scale >> down_shift) == 0) {
+            acumm = upset & 0x1;
+        }
         const u32 converted_value = (value * up_scale) >> down_shift;
-        return std::max<u32>(converted_value, 1U);
+        return std::max<u32>(converted_value + acumm, 1U);
     };
     if (src.enable) {
         scissor.offset.x = static_cast<s32>(scale_up(src.min_x));
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 02aac3b984..84ec803ba4 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -593,7 +593,7 @@ struct RangedBarrierRange {
 
 void BlitScale(VKScheduler& scheduler, VkImage src_image, VkImage dst_image, const ImageInfo& info,
                VkImageAspectFlags aspect_mask, const Settings::ResolutionScalingInfo& resolution,
-               bool up_scaling = true) {
+               bool is_bilinear, bool up_scaling = true) {
     const bool is_2d = info.type == ImageType::e2D;
     const auto resources = info.resources;
     const VkExtent2D extent{
@@ -602,7 +602,7 @@ void BlitScale(VKScheduler& scheduler, VkImage src_image, VkImage dst_image, con
     };
     const bool is_zeta = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0;
     const bool is_int_format = IsPixelFormatInteger(info.format);
-    const VkFilter vk_filter = (is_zeta || is_int_format) ? VK_FILTER_NEAREST : VK_FILTER_LINEAR;
+    const VkFilter vk_filter = is_bilinear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
 
     scheduler.RequestOutsideRenderPassOperationContext();
     scheduler.Record([dst_image, src_image, extent, resources, aspect_mask, resolution, is_2d,
@@ -1160,7 +1160,7 @@ bool Image::ScaleUp(bool ignore) {
     }
     current_image = *scaled_image;
     if (ignore) {
-      return true;
+        return true;
     }
 
     if (aspect_mask == 0) {
@@ -1170,11 +1170,18 @@ bool Image::ScaleUp(bool ignore) {
     const PixelFormat format = StorageFormat(info.format);
     const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format;
     const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
+    const bool is_color{aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT};
+    const bool is_bilinear{is_color && !IsPixelFormatInteger(info.format)};
     if (device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT)) {
-        BlitScale(*scheduler, *original_image, *scaled_image, info, aspect_mask, resolution);
+        BlitScale(*scheduler, *original_image, *scaled_image, info, aspect_mask, resolution,
+                  device.IsFormatSupported(vk_format,
+                                           VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT,
+                                           OPTIMAL_FORMAT));
     } else {
         using namespace VideoCommon;
         static constexpr auto BLIT_OPERATION = Tegra::Engines::Fermi2D::Operation::SrcCopy;
+        const auto operation = is_bilinear ? Tegra::Engines::Fermi2D::Filter::Bilinear
+                                           : Tegra::Engines::Fermi2D::Filter::Point;
 
         if (!scale_view) {
             const auto view_info = ImageViewInfo(ImageViewType::e2D, info.format);
@@ -1201,9 +1208,8 @@ bool Image::ScaleUp(bool ignore) {
             }
             const auto color_view = scale_view->Handle(Shader::TextureType::Color2D);
 
-            runtime->blit_image_helper.BlitColor(
-                scale_framebuffer.get(), color_view, dst_region, src_region,
-                Tegra::Engines::Fermi2D::Filter::Bilinear, BLIT_OPERATION);
+            runtime->blit_image_helper.BlitColor(scale_framebuffer.get(), color_view, dst_region,
+                                                 src_region, operation, BLIT_OPERATION);
         } else if (!runtime->device.IsBlitDepthStencilSupported() &&
                    aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
             if (!scale_framebuffer) {
@@ -1212,7 +1218,7 @@ bool Image::ScaleUp(bool ignore) {
             }
             runtime->blit_image_helper.BlitDepthStencil(
                 scale_framebuffer.get(), scale_view->DepthView(), scale_view->StencilView(),
-                dst_region, src_region, Tegra::Engines::Fermi2D::Filter::Point, BLIT_OPERATION);
+                dst_region, src_region, operation, BLIT_OPERATION);
         } else {
             // TODO: Use helper blits where applicable
             flags &= ~ImageFlagBits::Rescaled;
@@ -1233,8 +1239,8 @@ bool Image::ScaleDown(bool ignore) {
         return false;
     }
     if (ignore) {
-      current_image = *original_image;
-      return true;
+        current_image = *original_image;
+        return true;
     }
     const auto& device = runtime->device;
     const bool is_2d = info.type == ImageType::e2D;
@@ -1247,11 +1253,16 @@ bool Image::ScaleDown(bool ignore) {
     const PixelFormat format = StorageFormat(info.format);
     const auto vk_format = MaxwellToVK::SurfaceFormat(device, OPTIMAL_FORMAT, false, format).format;
     const auto blit_usage = VK_FORMAT_FEATURE_BLIT_SRC_BIT | VK_FORMAT_FEATURE_BLIT_DST_BIT;
+    const bool is_color{aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT};
+    const bool is_bilinear{is_color && !IsPixelFormatInteger(info.format)};
     if (device.IsFormatSupported(vk_format, blit_usage, OPTIMAL_FORMAT)) {
-        BlitScale(*scheduler, *scaled_image, *original_image, info, aspect_mask, resolution, false);
+        BlitScale(*scheduler, *scaled_image, *original_image, info, aspect_mask, resolution,
+                  is_bilinear, false);
     } else {
         using namespace VideoCommon;
         static constexpr auto BLIT_OPERATION = Tegra::Engines::Fermi2D::Operation::SrcCopy;
+        const auto operation = is_bilinear ? Tegra::Engines::Fermi2D::Filter::Bilinear
+                                           : Tegra::Engines::Fermi2D::Filter::Point;
 
         if (!normal_view) {
             const auto view_info = ImageViewInfo(ImageViewType::e2D, info.format);
@@ -1278,9 +1289,8 @@ bool Image::ScaleDown(bool ignore) {
             }
             const auto color_view = normal_view->Handle(Shader::TextureType::Color2D);
 
-            runtime->blit_image_helper.BlitColor(
-                normal_framebuffer.get(), color_view, dst_region, src_region,
-                Tegra::Engines::Fermi2D::Filter::Bilinear, BLIT_OPERATION);
+            runtime->blit_image_helper.BlitColor(normal_framebuffer.get(), color_view, dst_region,
+                                                 src_region, operation, BLIT_OPERATION);
         } else if (!runtime->device.IsBlitDepthStencilSupported() &&
                    aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) {
             if (!normal_framebuffer) {
@@ -1289,7 +1299,7 @@ bool Image::ScaleDown(bool ignore) {
             }
             runtime->blit_image_helper.BlitDepthStencil(
                 normal_framebuffer.get(), normal_view->DepthView(), normal_view->StencilView(),
-                dst_region, src_region, Tegra::Engines::Fermi2D::Filter::Point, BLIT_OPERATION);
+                dst_region, src_region, operation, BLIT_OPERATION);
         } else {
             // TODO: Use helper blits where applicable
             flags &= ~ImageFlagBits::Rescaled;