From 6b00443bc1c8379794f82f07636bed05499c2779 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Fri, 15 Jan 2021 21:37:17 -0300
Subject: [PATCH] vk_texture_cache: Support image store on sRGB images with
 VkImageViewUsageCreateInfo

Vulkan 1.0 didn't support creating sRGB image views on an ABGR8 VkImage
with storage usage bits. VK_KHR_maintenance2 addressed this allowing to
reduce the usage bits on a VkImageView.

To allow image store on non-sRGB image views when the VkImage is created
with sRGB, always create VkImages without sRGB and add the sRGB format
on the view.
---
 .../renderer_vulkan/maxwell_to_vk.cpp         | 27 ++++---
 .../renderer_vulkan/maxwell_to_vk.h           | 10 ++-
 .../renderer_vulkan/vk_texture_cache.cpp      | 73 ++++++++++++-------
 3 files changed, 72 insertions(+), 38 deletions(-)

diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index ca7c2c579e..b4473f1940 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -110,8 +110,8 @@ VkCompareOp DepthCompareFunction(Tegra::Texture::DepthCompareFunc depth_compare_
 } // namespace Sampler
 
 namespace {
-
-enum : u32 { Attachable = 1, Storage = 2 };
+constexpr u32 Attachable = 1 << 0;
+constexpr u32 Storage = 1 << 1;
 
 struct FormatTuple {
     VkFormat format; ///< Vulkan format
@@ -222,22 +222,27 @@ constexpr bool IsZetaFormat(PixelFormat pixel_format) {
 
 } // Anonymous namespace
 
-FormatInfo SurfaceFormat(const Device& device, FormatType format_type, PixelFormat pixel_format) {
-    ASSERT(static_cast<std::size_t>(pixel_format) < std::size(tex_format_tuples));
-
-    auto tuple = tex_format_tuples[static_cast<std::size_t>(pixel_format)];
+FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
+                         PixelFormat pixel_format) {
+    ASSERT(static_cast<size_t>(pixel_format) < std::size(tex_format_tuples));
+    FormatTuple tuple = tex_format_tuples[static_cast<size_t>(pixel_format)];
     if (tuple.format == VK_FORMAT_UNDEFINED) {
         UNIMPLEMENTED_MSG("Unimplemented texture format with pixel format={}", pixel_format);
-        return {VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true};
+        return FormatInfo{VK_FORMAT_A8B8G8R8_UNORM_PACK32, true, true};
     }
 
     // Use A8B8G8R8_UNORM on hardware that doesn't support ASTC natively
     if (!device.IsOptimalAstcSupported() && VideoCore::Surface::IsPixelFormatASTC(pixel_format)) {
-        const bool is_srgb = VideoCore::Surface::IsPixelFormatSRGB(pixel_format);
-        tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+        const bool is_srgb = with_srgb && VideoCore::Surface::IsPixelFormatSRGB(pixel_format);
+        if (is_srgb) {
+            tuple.format = VK_FORMAT_A8B8G8R8_SRGB_PACK32;
+        } else {
+            tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+            tuple.usage |= Storage;
+        }
     }
-    const bool attachable = tuple.usage & Attachable;
-    const bool storage = tuple.usage & Storage;
+    const bool attachable = (tuple.usage & Attachable) != 0;
+    const bool storage = (tuple.usage & Storage) != 0;
 
     VkFormatFeatureFlags usage{};
     switch (format_type) {
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.h b/src/video_core/renderer_vulkan/maxwell_to_vk.h
index 5379698402..e59e6b6e21 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.h
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.h
@@ -35,7 +35,15 @@ struct FormatInfo {
     bool storage;
 };
 
-FormatInfo SurfaceFormat(const Device& device, FormatType format_type, PixelFormat pixel_format);
+/**
+ * Returns format properties supported in the host
+ * @param device       Host device
+ * @param format_type  Type of image the buffer will use
+ * @param with_srgb    True when the format can be sRGB when converted to another format (ASTC)
+ * @param pixel_format Guest pixel format to describe
+ */
+[[nodiscard]] FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with_srgb,
+                                       PixelFormat pixel_format);
 
 VkShaderStageFlagBits ShaderStage(Tegra::Engines::ShaderType stage);
 
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index bd11de012c..0f9e93e483 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -95,20 +95,12 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
     }
 }
 
-[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
-    const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, info.format);
-    VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
-    if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
-        info.size.width == info.size.height) {
-        flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
-    }
-    if (info.type == ImageType::e3D) {
-        flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
-    }
+[[nodiscard]] VkImageUsageFlags ImageUsageFlags(const MaxwellToVK::FormatInfo& info,
+                                                PixelFormat format) {
     VkImageUsageFlags usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
                               VK_IMAGE_USAGE_SAMPLED_BIT;
-    if (format_info.attachable) {
-        switch (VideoCore::Surface::GetFormatType(info.format)) {
+    if (info.attachable) {
+        switch (VideoCore::Surface::GetFormatType(format)) {
         case VideoCore::Surface::SurfaceType::ColorTexture:
             usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
             break;
@@ -120,9 +112,33 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
             UNREACHABLE_MSG("Invalid surface type");
         }
     }
-    if (format_info.storage) {
+    if (info.storage) {
         usage |= VK_IMAGE_USAGE_STORAGE_BIT;
     }
+    return usage;
+}
+
+/// Returns the preferred format for a VkImage
+[[nodiscard]] PixelFormat StorageFormat(PixelFormat format) {
+    switch (format) {
+    case PixelFormat::A8B8G8R8_SRGB:
+        return PixelFormat::A8B8G8R8_UNORM;
+    default:
+        return format;
+    }
+}
+
+[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
+    const PixelFormat format = StorageFormat(info.format);
+    const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format);
+    VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
+    if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
+        info.size.width == info.size.height) {
+        flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
+    }
+    if (info.type == ImageType::e3D) {
+        flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT;
+    }
     const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples);
     return VkImageCreateInfo{
         .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
@@ -130,17 +146,16 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
         .flags = flags,
         .imageType = ConvertImageType(info.type),
         .format = format_info.format,
-        .extent =
-            {
-                .width = info.size.width >> samples_x,
-                .height = info.size.height >> samples_y,
-                .depth = info.size.depth,
-            },
+        .extent{
+            .width = info.size.width >> samples_x,
+            .height = info.size.height >> samples_y,
+            .depth = info.size.depth,
+        },
         .mipLevels = static_cast<u32>(info.resources.levels),
         .arrayLayers = static_cast<u32>(info.resources.layers),
         .samples = ConvertSampleCount(info.num_samples),
         .tiling = VK_IMAGE_TILING_OPTIMAL,
-        .usage = usage,
+        .usage = ImageUsageFlags(format_info, format),
         .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
         .queueFamilyIndexCount = 0,
         .pQueueFamilyIndices = nullptr,
@@ -209,10 +224,11 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
 
 [[nodiscard]] VkAttachmentDescription AttachmentDescription(const Device& device,
                                                             const ImageView* image_view) {
-    const auto pixel_format = image_view->format;
+    using MaxwellToVK::SurfaceFormat;
+    const PixelFormat pixel_format = image_view->format;
     return VkAttachmentDescription{
         .flags = VK_ATTACHMENT_DESCRIPTION_MAY_ALIAS_BIT,
-        .format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, pixel_format).format,
+        .format = SurfaceFormat(device, FormatType::Optimal, true, pixel_format).format,
         .samples = image_view->Samples(),
         .loadOp = VK_ATTACHMENT_LOAD_OP_LOAD,
         .storeOp = VK_ATTACHMENT_STORE_OP_STORE,
@@ -860,11 +876,16 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
             std::ranges::transform(swizzle, swizzle.begin(), ConvertGreenRed);
         }
     }
-    const VkFormat vk_format =
-        MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, format).format;
+    const auto format_info = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format);
+    const VkFormat vk_format = format_info.format;
+    const VkImageViewUsageCreateInfo image_view_usage{
+        .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO,
+        .pNext = nullptr,
+        .usage = ImageUsageFlags(format_info, format),
+    };
     const VkImageViewCreateInfo create_info{
         .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
-        .pNext = nullptr,
+        .pNext = &image_view_usage,
         .flags = 0,
         .image = image.Handle(),
         .viewType = VkImageViewType{},
@@ -954,7 +975,7 @@ vk::ImageView ImageView::MakeDepthStencilView(VkImageAspectFlags aspect_mask) {
         .flags = 0,
         .image = image_handle,
         .viewType = ImageViewType(type),
-        .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, format).format,
+        .format = MaxwellToVK::SurfaceFormat(*device, FormatType::Optimal, true, format).format,
         .components{
             .r = VK_COMPONENT_SWIZZLE_IDENTITY,
             .g = VK_COMPONENT_SWIZZLE_IDENTITY,