/ src / video_core / renderer_vulkan / vk_texture_runtime.cpp
vk_texture_runtime.cpp
   1  // Copyright 2023 Citra Emulator Project
   2  // Licensed under GPLv2 or any later version
   3  // Refer to the license.txt file included.
   4  
   5  #include <boost/container/small_vector.hpp>
   6  #include <boost/container/static_vector.hpp>
   7  
   8  #include "common/literals.h"
   9  #include "common/microprofile.h"
  10  #include "common/scope_exit.h"
  11  #include "video_core/custom_textures/material.h"
  12  #include "video_core/rasterizer_cache/texture_codec.h"
  13  #include "video_core/rasterizer_cache/utils.h"
  14  #include "video_core/renderer_vulkan/pica_to_vk.h"
  15  #include "video_core/renderer_vulkan/vk_descriptor_pool.h"
  16  #include "video_core/renderer_vulkan/vk_instance.h"
  17  #include "video_core/renderer_vulkan/vk_renderpass_cache.h"
  18  #include "video_core/renderer_vulkan/vk_scheduler.h"
  19  #include "video_core/renderer_vulkan/vk_texture_runtime.h"
  20  
  21  #include <vk_mem_alloc.h>
  22  #include <vulkan/vulkan_format_traits.hpp>
  23  
  24  // Ignore the -Wclass-memaccess warning on memcpy for non-trivially default constructible objects.
  25  #if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
  26  #pragma GCC diagnostic push
  27  #pragma GCC diagnostic ignored "-Wclass-memaccess"
  28  #endif
  29  
  30  MICROPROFILE_DEFINE(Vulkan_ImageAlloc, "Vulkan", "Texture Allocation", MP_RGB(192, 52, 235));
  31  
  32  namespace Vulkan {
  33  
  34  namespace {
  35  
  36  using VideoCore::MapType;
  37  using VideoCore::PixelFormat;
  38  using VideoCore::SurfaceType;
  39  using VideoCore::TextureType;
  40  using namespace Common::Literals;
  41  
  42  struct RecordParams {
  43      vk::ImageAspectFlags aspect;
  44      vk::Filter filter;
  45      vk::PipelineStageFlags pipeline_flags;
  46      vk::AccessFlags src_access;
  47      vk::AccessFlags dst_access;
  48      vk::Image src_image;
  49      vk::Image dst_image;
  50  };
  51  
  52  vk::Filter MakeFilter(VideoCore::PixelFormat pixel_format) {
  53      switch (pixel_format) {
  54      case VideoCore::PixelFormat::D16:
  55      case VideoCore::PixelFormat::D24:
  56      case VideoCore::PixelFormat::D24S8:
  57          return vk::Filter::eNearest;
  58      default:
  59          return vk::Filter::eLinear;
  60      }
  61  }
  62  
  63  [[nodiscard]] vk::ClearValue MakeClearValue(VideoCore::ClearValue clear) {
  64      static_assert(sizeof(VideoCore::ClearValue) == sizeof(vk::ClearValue));
  65  
  66      vk::ClearValue value{};
  67      std::memcpy(&value, &clear, sizeof(vk::ClearValue));
  68      return value;
  69  }
  70  
  71  [[nodiscard]] vk::ClearColorValue MakeClearColorValue(Common::Vec4f color) {
  72      return vk::ClearColorValue{
  73          .float32 = std::array{color[0], color[1], color[2], color[3]},
  74      };
  75  }
  76  
  77  [[nodiscard]] vk::ClearDepthStencilValue MakeClearDepthStencilValue(VideoCore::ClearValue clear) {
  78      return vk::ClearDepthStencilValue{
  79          .depth = clear.depth,
  80          .stencil = clear.stencil,
  81      };
  82  }
  83  
  84  u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) {
  85      u32 depth_offset = 0;
  86      u32 stencil_offset = 4 * data.size / 5;
  87      const auto& mapped = data.mapped;
  88  
  89      switch (dest) {
  90      case vk::Format::eD24UnormS8Uint: {
  91          for (; stencil_offset < data.size; depth_offset += 4) {
  92              u8* ptr = mapped.data() + depth_offset;
  93              const u32 d24s8 = VideoCore::MakeInt<u32>(ptr);
  94              const u32 d24 = d24s8 >> 8;
  95              mapped[stencil_offset] = d24s8 & 0xFF;
  96              std::memcpy(ptr, &d24, 4);
  97              stencil_offset++;
  98          }
  99          break;
 100      }
 101      case vk::Format::eD32SfloatS8Uint: {
 102          for (; stencil_offset < data.size; depth_offset += 4) {
 103              u8* ptr = mapped.data() + depth_offset;
 104              const u32 d24s8 = VideoCore::MakeInt<u32>(ptr);
 105              const float d32 = (d24s8 >> 8) / 16777215.f;
 106              mapped[stencil_offset] = d24s8 & 0xFF;
 107              std::memcpy(ptr, &d32, 4);
 108              stencil_offset++;
 109          }
 110          break;
 111      }
 112      default:
 113          LOG_ERROR(Render_Vulkan, "Unimplemented convertion for depth format {}",
 114                    vk::to_string(dest));
 115          UNREACHABLE();
 116      }
 117  
 118      ASSERT(depth_offset == 4 * data.size / 5);
 119      return depth_offset;
 120  }
 121  
 122  boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
 123      vk::ImageAspectFlags aspect, std::span<const vk::Image> images) {
 124      boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers;
 125      for (const vk::Image& image : images) {
 126          barriers.push_back(vk::ImageMemoryBarrier{
 127              .srcAccessMask = vk::AccessFlagBits::eNone,
 128              .dstAccessMask = vk::AccessFlagBits::eNone,
 129              .oldLayout = vk::ImageLayout::eUndefined,
 130              .newLayout = vk::ImageLayout::eGeneral,
 131              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 132              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 133              .image = image,
 134              .subresourceRange{
 135                  .aspectMask = aspect,
 136                  .baseMipLevel = 0,
 137                  .levelCount = VK_REMAINING_MIP_LEVELS,
 138                  .baseArrayLayer = 0,
 139                  .layerCount = VK_REMAINING_ARRAY_LAYERS,
 140              },
 141          });
 142      }
 143      return barriers;
 144  }
 145  
 146  Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, TextureType type,
 147                    vk::Format format, vk::ImageUsageFlags usage, vk::ImageCreateFlags flags,
 148                    vk::ImageAspectFlags aspect, bool need_format_list,
 149                    std::string_view debug_name = {}) {
 150      const u32 layers = type == TextureType::CubeMap ? 6 : 1;
 151  
 152      const std::array format_list = {
 153          vk::Format::eR8G8B8A8Unorm,
 154          vk::Format::eR32Uint,
 155      };
 156      const vk::ImageFormatListCreateInfo image_format_list = {
 157          .viewFormatCount = static_cast<u32>(format_list.size()),
 158          .pViewFormats = format_list.data(),
 159      };
 160  
 161      const vk::ImageCreateInfo image_info = {
 162          .pNext = need_format_list ? &image_format_list : nullptr,
 163          .flags = flags,
 164          .imageType = vk::ImageType::e2D,
 165          .format = format,
 166          .extent = {width, height, 1},
 167          .mipLevels = levels,
 168          .arrayLayers = layers,
 169          .samples = vk::SampleCountFlagBits::e1,
 170          .usage = usage,
 171      };
 172  
 173      const VmaAllocationCreateInfo alloc_info = {
 174          .flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
 175          .usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
 176          .requiredFlags = 0,
 177          .preferredFlags = 0,
 178          .pool = VK_NULL_HANDLE,
 179          .pUserData = nullptr,
 180      };
 181  
 182      VkImage unsafe_image{};
 183      VkImageCreateInfo unsafe_image_info = static_cast<VkImageCreateInfo>(image_info);
 184      VmaAllocation allocation{};
 185  
 186      VkResult result = vmaCreateImage(instance->GetAllocator(), &unsafe_image_info, &alloc_info,
 187                                       &unsafe_image, &allocation, nullptr);
 188      if (result != VK_SUCCESS) [[unlikely]] {
 189          LOG_CRITICAL(Render_Vulkan, "Failed allocating image with error {}", result);
 190          UNREACHABLE();
 191      }
 192  
 193      const vk::Image image{unsafe_image};
 194      const vk::ImageViewCreateInfo view_info = {
 195          .image = image,
 196          .viewType =
 197              type == TextureType::CubeMap ? vk::ImageViewType::eCube : vk::ImageViewType::e2D,
 198          .format = format,
 199          .subresourceRange{
 200              .aspectMask = aspect,
 201              .baseMipLevel = 0,
 202              .levelCount = levels,
 203              .baseArrayLayer = 0,
 204              .layerCount = layers,
 205          },
 206      };
 207      vk::UniqueImageView image_view = instance->GetDevice().createImageViewUnique(view_info);
 208  
 209      if (!debug_name.empty() && instance->HasDebuggingToolAttached()) {
 210          Vulkan::SetObjectName(instance->GetDevice(), image, debug_name);
 211          Vulkan::SetObjectName(instance->GetDevice(), image_view.get(), "{} View({})", debug_name,
 212                                vk::to_string(aspect));
 213      }
 214  
 215      return Handle{
 216          .alloc = allocation,
 217          .image = image,
 218          .image_view = std::move(image_view),
 219      };
 220  }
 221  
 222  vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width,
 223                                        u32 height, std::span<const vk::ImageView> attachments) {
 224      const vk::FramebufferCreateInfo framebuffer_info = {
 225          .renderPass = render_pass,
 226          .attachmentCount = static_cast<u32>(attachments.size()),
 227          .pAttachments = attachments.data(),
 228          .width = width,
 229          .height = height,
 230          .layers = 1,
 231      };
 232      return device.createFramebufferUnique(framebuffer_info);
 233  }
 234  
 235  vk::ImageSubresourceRange MakeSubresourceRange(vk::ImageAspectFlags aspect, u32 level = 0,
 236                                                 u32 levels = 1, u32 layer = 0) {
 237      return vk::ImageSubresourceRange{
 238          .aspectMask = aspect,
 239          .baseMipLevel = level,
 240          .levelCount = levels,
 241          .baseArrayLayer = layer,
 242          .layerCount = VK_REMAINING_ARRAY_LAYERS,
 243      };
 244  }
 245  
 246  constexpr u64 UPLOAD_BUFFER_SIZE = 512_MiB;
 247  constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB;
 248  
 249  } // Anonymous namespace
 250  
 251  TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
 252                                 RenderpassCache& renderpass_cache, DescriptorPool& pool,
 253                                 DescriptorSetProvider& texture_provider_, u32 num_swapchain_images_)
 254      : instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache},
 255        texture_provider{texture_provider_}, blit_helper{instance, scheduler, pool, renderpass_cache},
 256        upload_buffer{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, UPLOAD_BUFFER_SIZE,
 257                      BufferType::Upload},
 258        download_buffer{instance, scheduler,
 259                        vk::BufferUsageFlagBits::eTransferDst |
 260                            vk::BufferUsageFlagBits::eStorageBuffer,
 261                        DOWNLOAD_BUFFER_SIZE, BufferType::Download},
 262        num_swapchain_images{num_swapchain_images_} {}
 263  
 264  TextureRuntime::~TextureRuntime() = default;
 265  
 266  VideoCore::StagingData TextureRuntime::FindStaging(u32 size, bool upload) {
 267      StreamBuffer& buffer = upload ? upload_buffer : download_buffer;
 268      const auto [data, offset, invalidate] = buffer.Map(size, 16);
 269      return VideoCore::StagingData{
 270          .size = size,
 271          .offset = static_cast<u32>(offset),
 272          .mapped = std::span{data, size},
 273      };
 274  }
 275  
 276  u32 TextureRuntime::RemoveThreshold() {
 277      return num_swapchain_images;
 278  }
 279  
 280  void TextureRuntime::Finish() {
 281      scheduler.Finish();
 282  }
 283  
 284  bool TextureRuntime::Reinterpret(Surface& source, Surface& dest,
 285                                   const VideoCore::TextureCopy& copy) {
 286      const PixelFormat src_format = source.pixel_format;
 287      const PixelFormat dst_format = dest.pixel_format;
 288      ASSERT_MSG(src_format != dst_format, "Reinterpretation with the same format is invalid");
 289  
 290      if (!source.traits.needs_conversion && !dest.traits.needs_conversion &&
 291          source.type == dest.type) {
 292          CopyTextures(source, dest, copy);
 293          return true;
 294      }
 295  
 296      if (src_format == PixelFormat::D24S8 && dst_format == PixelFormat::RGBA8) {
 297          blit_helper.ConvertDS24S8ToRGBA8(source, dest, copy);
 298      } else {
 299          LOG_WARNING(Render_Vulkan, "Unimplemented reinterpretation {} -> {}",
 300                      VideoCore::PixelFormatAsString(src_format),
 301                      VideoCore::PixelFormatAsString(dst_format));
 302          return false;
 303      }
 304      return true;
 305  }
 306  
 307  bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) {
 308      renderpass_cache.EndRendering();
 309  
 310      const RecordParams params = {
 311          .aspect = surface.Aspect(),
 312          .pipeline_flags = surface.PipelineStageFlags(),
 313          .src_access = surface.AccessFlags(),
 314          .src_image = surface.Image(),
 315      };
 316  
 317      if (clear.texture_rect == surface.GetScaledRect()) {
 318          scheduler.Record([params, clear](vk::CommandBuffer cmdbuf) {
 319              const vk::ImageSubresourceRange range = {
 320                  .aspectMask = params.aspect,
 321                  .baseMipLevel = clear.texture_level,
 322                  .levelCount = 1,
 323                  .baseArrayLayer = 0,
 324                  .layerCount = VK_REMAINING_ARRAY_LAYERS,
 325              };
 326  
 327              const vk::ImageMemoryBarrier pre_barrier = {
 328                  .srcAccessMask = params.src_access,
 329                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 330                  .oldLayout = vk::ImageLayout::eGeneral,
 331                  .newLayout = vk::ImageLayout::eTransferDstOptimal,
 332                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 333                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 334                  .image = params.src_image,
 335                  .subresourceRange = range,
 336              };
 337  
 338              const vk::ImageMemoryBarrier post_barrier = {
 339                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 340                  .dstAccessMask = params.src_access,
 341                  .oldLayout = vk::ImageLayout::eTransferDstOptimal,
 342                  .newLayout = vk::ImageLayout::eGeneral,
 343                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 344                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 345                  .image = params.src_image,
 346                  .subresourceRange = range,
 347              };
 348  
 349              cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
 350                                     vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier);
 351  
 352              const bool is_color =
 353                  static_cast<bool>(params.aspect & vk::ImageAspectFlagBits::eColor);
 354              if (is_color) {
 355                  cmdbuf.clearColorImage(params.src_image, vk::ImageLayout::eTransferDstOptimal,
 356                                         MakeClearColorValue(clear.value.color), range);
 357              } else {
 358                  cmdbuf.clearDepthStencilImage(params.src_image,
 359                                                vk::ImageLayout::eTransferDstOptimal,
 360                                                MakeClearDepthStencilValue(clear.value), range);
 361              }
 362  
 363              cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
 364                                     vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
 365          });
 366          return true;
 367      }
 368  
 369      ClearTextureWithRenderpass(surface, clear);
 370      return true;
 371  }
 372  
 373  void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
 374                                                  const VideoCore::TextureClear& clear) {
 375      const bool is_color = surface.type != VideoCore::SurfaceType::Depth &&
 376                            surface.type != VideoCore::SurfaceType::DepthStencil;
 377  
 378      const auto color_format = is_color ? surface.pixel_format : PixelFormat::Invalid;
 379      const auto depth_format = is_color ? PixelFormat::Invalid : surface.pixel_format;
 380      const auto render_pass = renderpass_cache.GetRenderpass(color_format, depth_format, true);
 381  
 382      const RecordParams params = {
 383          .aspect = surface.Aspect(),
 384          .pipeline_flags = surface.PipelineStageFlags(),
 385          .src_access = surface.AccessFlags(),
 386          .src_image = surface.Image(),
 387      };
 388  
 389      scheduler.Record([params, is_color, clear, render_pass,
 390                        framebuffer = surface.Framebuffer()](vk::CommandBuffer cmdbuf) {
 391          const vk::AccessFlags access_flag =
 392              is_color ? vk::AccessFlagBits::eColorAttachmentRead |
 393                             vk::AccessFlagBits::eColorAttachmentWrite
 394                       : vk::AccessFlagBits::eDepthStencilAttachmentRead |
 395                             vk::AccessFlagBits::eDepthStencilAttachmentWrite;
 396  
 397          const vk::PipelineStageFlags pipeline_flags =
 398              is_color ? vk::PipelineStageFlagBits::eColorAttachmentOutput
 399                       : vk::PipelineStageFlagBits::eEarlyFragmentTests;
 400  
 401          const vk::ImageMemoryBarrier pre_barrier = {
 402              .srcAccessMask = params.src_access,
 403              .dstAccessMask = access_flag,
 404              .oldLayout = vk::ImageLayout::eGeneral,
 405              .newLayout = vk::ImageLayout::eGeneral,
 406              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 407              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 408              .image = params.src_image,
 409              .subresourceRange = MakeSubresourceRange(params.aspect, clear.texture_level),
 410          };
 411  
 412          const vk::ImageMemoryBarrier post_barrier = {
 413              .srcAccessMask = access_flag,
 414              .dstAccessMask = params.src_access,
 415              .oldLayout = vk::ImageLayout::eGeneral,
 416              .newLayout = vk::ImageLayout::eGeneral,
 417              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 418              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 419              .image = params.src_image,
 420              .subresourceRange = MakeSubresourceRange(params.aspect, clear.texture_level),
 421          };
 422  
 423          const vk::Rect2D render_area = {
 424              .offset{
 425                  .x = static_cast<s32>(clear.texture_rect.left),
 426                  .y = static_cast<s32>(clear.texture_rect.bottom),
 427              },
 428              .extent{
 429                  .width = clear.texture_rect.GetWidth(),
 430                  .height = clear.texture_rect.GetHeight(),
 431              },
 432          };
 433  
 434          const auto clear_value = MakeClearValue(clear.value);
 435  
 436          const vk::RenderPassBeginInfo renderpass_begin_info = {
 437              .renderPass = render_pass,
 438              .framebuffer = framebuffer,
 439              .renderArea = render_area,
 440              .clearValueCount = 1,
 441              .pClearValues = &clear_value,
 442          };
 443  
 444          cmdbuf.pipelineBarrier(params.pipeline_flags, pipeline_flags,
 445                                 vk::DependencyFlagBits::eByRegion, {}, {}, pre_barrier);
 446  
 447          cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
 448          cmdbuf.endRenderPass();
 449  
 450          cmdbuf.pipelineBarrier(pipeline_flags, params.pipeline_flags,
 451                                 vk::DependencyFlagBits::eByRegion, {}, {}, post_barrier);
 452      });
 453  }
 454  
 455  bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
 456                                    const VideoCore::TextureCopy& copy) {
 457      renderpass_cache.EndRendering();
 458  
 459      const RecordParams params = {
 460          .aspect = source.Aspect(),
 461          .filter = MakeFilter(source.pixel_format),
 462          .pipeline_flags = source.PipelineStageFlags() | dest.PipelineStageFlags(),
 463          .src_access = source.AccessFlags(),
 464          .dst_access = dest.AccessFlags(),
 465          .src_image = source.Image(),
 466          .dst_image = dest.Image(),
 467      };
 468  
 469      scheduler.Record([params, copy](vk::CommandBuffer cmdbuf) {
 470          const vk::ImageCopy image_copy = {
 471              .srcSubresource{
 472                  .aspectMask = params.aspect,
 473                  .mipLevel = copy.src_level,
 474                  .baseArrayLayer = copy.src_layer,
 475                  .layerCount = 1,
 476              },
 477              .srcOffset = {static_cast<s32>(copy.src_offset.x), static_cast<s32>(copy.src_offset.y),
 478                            0},
 479              .dstSubresource{
 480                  .aspectMask = params.aspect,
 481                  .mipLevel = copy.dst_level,
 482                  .baseArrayLayer = copy.dst_layer,
 483                  .layerCount = 1,
 484              },
 485              .dstOffset = {static_cast<s32>(copy.dst_offset.x), static_cast<s32>(copy.dst_offset.y),
 486                            0},
 487              .extent = {copy.extent.width, copy.extent.height, 1},
 488          };
 489  
 490          const bool self_copy = params.src_image == params.dst_image;
 491          const vk::ImageLayout new_src_layout =
 492              self_copy ? vk::ImageLayout::eGeneral : vk::ImageLayout::eTransferSrcOptimal;
 493          const vk::ImageLayout new_dst_layout =
 494              self_copy ? vk::ImageLayout::eGeneral : vk::ImageLayout::eTransferDstOptimal;
 495  
 496          const std::array pre_barriers = {
 497              vk::ImageMemoryBarrier{
 498                  .srcAccessMask = params.src_access,
 499                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
 500                  .oldLayout = vk::ImageLayout::eGeneral,
 501                  .newLayout = new_src_layout,
 502                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 503                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 504                  .image = params.src_image,
 505                  .subresourceRange = MakeSubresourceRange(params.aspect, copy.src_level),
 506              },
 507              vk::ImageMemoryBarrier{
 508                  .srcAccessMask = params.dst_access,
 509                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 510                  .oldLayout = vk::ImageLayout::eGeneral,
 511                  .newLayout = new_dst_layout,
 512                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 513                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 514                  .image = params.dst_image,
 515                  .subresourceRange = MakeSubresourceRange(params.aspect, copy.dst_level),
 516              },
 517          };
 518          const std::array post_barriers = {
 519              vk::ImageMemoryBarrier{
 520                  .srcAccessMask = vk::AccessFlagBits::eNone,
 521                  .dstAccessMask = vk::AccessFlagBits::eNone,
 522                  .oldLayout = new_src_layout,
 523                  .newLayout = vk::ImageLayout::eGeneral,
 524                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 525                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 526                  .image = params.src_image,
 527                  .subresourceRange = MakeSubresourceRange(params.aspect, copy.src_level),
 528              },
 529              vk::ImageMemoryBarrier{
 530                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 531                  .dstAccessMask = params.dst_access,
 532                  .oldLayout = new_dst_layout,
 533                  .newLayout = vk::ImageLayout::eGeneral,
 534                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 535                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 536                  .image = params.dst_image,
 537                  .subresourceRange = MakeSubresourceRange(params.aspect, copy.dst_level),
 538              },
 539          };
 540  
 541          cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
 542                                 vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
 543  
 544          cmdbuf.copyImage(params.src_image, new_src_layout, params.dst_image, new_dst_layout,
 545                           image_copy);
 546  
 547          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
 548                                 vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers);
 549      });
 550  
 551      return true;
 552  }
 553  
 554  bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
 555                                    const VideoCore::TextureBlit& blit) {
 556      const bool is_depth_stencil = source.type == VideoCore::SurfaceType::DepthStencil;
 557      const auto& depth_traits = instance.GetTraits(source.pixel_format);
 558      if (is_depth_stencil && !depth_traits.blit_support) {
 559          return blit_helper.BlitDepthStencil(source, dest, blit);
 560      }
 561  
 562      renderpass_cache.EndRendering();
 563  
 564      const RecordParams params = {
 565          .aspect = source.Aspect(),
 566          .filter = MakeFilter(source.pixel_format),
 567          .pipeline_flags = source.PipelineStageFlags() | dest.PipelineStageFlags(),
 568          .src_access = source.AccessFlags(),
 569          .dst_access = dest.AccessFlags(),
 570          .src_image = source.Image(),
 571          .dst_image = dest.Image(),
 572      };
 573  
 574      scheduler.Record([params, blit](vk::CommandBuffer cmdbuf) {
 575          const std::array source_offsets = {
 576              vk::Offset3D{static_cast<s32>(blit.src_rect.left),
 577                           static_cast<s32>(blit.src_rect.bottom), 0},
 578              vk::Offset3D{static_cast<s32>(blit.src_rect.right), static_cast<s32>(blit.src_rect.top),
 579                           1},
 580          };
 581  
 582          const std::array dest_offsets = {
 583              vk::Offset3D{static_cast<s32>(blit.dst_rect.left),
 584                           static_cast<s32>(blit.dst_rect.bottom), 0},
 585              vk::Offset3D{static_cast<s32>(blit.dst_rect.right), static_cast<s32>(blit.dst_rect.top),
 586                           1},
 587          };
 588  
 589          const vk::ImageBlit blit_area = {
 590              .srcSubresource{
 591                  .aspectMask = params.aspect,
 592                  .mipLevel = blit.src_level,
 593                  .baseArrayLayer = blit.src_layer,
 594                  .layerCount = 1,
 595              },
 596              .srcOffsets = source_offsets,
 597              .dstSubresource{
 598                  .aspectMask = params.aspect,
 599                  .mipLevel = blit.dst_level,
 600                  .baseArrayLayer = blit.dst_layer,
 601                  .layerCount = 1,
 602              },
 603              .dstOffsets = dest_offsets,
 604          };
 605  
 606          const std::array read_barriers = {
 607              vk::ImageMemoryBarrier{
 608                  .srcAccessMask = params.src_access,
 609                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
 610                  .oldLayout = vk::ImageLayout::eGeneral,
 611                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
 612                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 613                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 614                  .image = params.src_image,
 615                  .subresourceRange = MakeSubresourceRange(params.aspect, blit.src_level),
 616              },
 617              vk::ImageMemoryBarrier{
 618                  .srcAccessMask = params.dst_access,
 619                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 620                  .oldLayout = vk::ImageLayout::eGeneral,
 621                  .newLayout = vk::ImageLayout::eTransferDstOptimal,
 622                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 623                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 624                  .image = params.dst_image,
 625                  .subresourceRange = MakeSubresourceRange(params.aspect, blit.dst_level),
 626              },
 627          };
 628          const std::array write_barriers = {
 629              vk::ImageMemoryBarrier{
 630                  .srcAccessMask = vk::AccessFlagBits::eTransferRead,
 631                  .dstAccessMask = params.src_access,
 632                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
 633                  .newLayout = vk::ImageLayout::eGeneral,
 634                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 635                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 636                  .image = params.src_image,
 637                  .subresourceRange = MakeSubresourceRange(params.aspect, blit.src_level),
 638              },
 639              vk::ImageMemoryBarrier{
 640                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 641                  .dstAccessMask = params.dst_access,
 642                  .oldLayout = vk::ImageLayout::eTransferDstOptimal,
 643                  .newLayout = vk::ImageLayout::eGeneral,
 644                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 645                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 646                  .image = params.dst_image,
 647                  .subresourceRange = MakeSubresourceRange(params.aspect, blit.dst_level),
 648              },
 649          };
 650  
 651          cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
 652                                 vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers);
 653  
 654          cmdbuf.blitImage(params.src_image, vk::ImageLayout::eTransferSrcOptimal, params.dst_image,
 655                           vk::ImageLayout::eTransferDstOptimal, blit_area, params.filter);
 656  
 657          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
 658                                 vk::DependencyFlagBits::eByRegion, {}, {}, write_barriers);
 659      });
 660  
 661      return true;
 662  }
 663  
 664  void TextureRuntime::GenerateMipmaps(Surface& surface) {
 665      if (VideoCore::IsCustomFormatCompressed(surface.custom_format)) {
 666          LOG_ERROR(Render_Vulkan, "Generating mipmaps for compressed formats unsupported!");
 667          return;
 668      }
 669  
 670      renderpass_cache.EndRendering();
 671  
 672      auto [width, height] = surface.RealExtent();
 673      const u32 levels = surface.levels;
 674      for (u32 i = 1; i < levels; i++) {
 675          const Common::Rectangle<u32> src_rect{0, height, width, 0};
 676          width = width > 1 ? width >> 1 : 1;
 677          height = height > 1 ? height >> 1 : 1;
 678          const Common::Rectangle<u32> dst_rect{0, height, width, 0};
 679  
 680          const VideoCore::TextureBlit blit = {
 681              .src_level = i - 1,
 682              .dst_level = i,
 683              .src_rect = src_rect,
 684              .dst_rect = dst_rect,
 685          };
 686          BlitTextures(surface, surface, blit);
 687      }
 688  }
 689  
 690  bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat format) const {
 691      const FormatTraits traits = instance.GetTraits(format);
 692      return traits.needs_conversion &&
 693             // DepthStencil formats are handled elsewhere due to de-interleaving.
 694             traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
 695  }
 696  
 697  void TextureRuntime::FreeDescriptorSetsWithImage(vk::ImageView image_view) {
 698      texture_provider.FreeWithImage(image_view);
 699      blit_helper.compute_provider.FreeWithImage(image_view);
 700      blit_helper.compute_buffer_provider.FreeWithImage(image_view);
 701      blit_helper.two_textures_provider.FreeWithImage(image_view);
 702  }
 703  
 704  Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
 705      : SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
 706        scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
 707  
 708      if (pixel_format == VideoCore::PixelFormat::Invalid) {
 709          return;
 710      }
 711  
 712      const bool is_mutable = pixel_format == VideoCore::PixelFormat::RGBA8;
 713      const vk::Format format = traits.native;
 714  
 715      ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1,
 716                 "Image allocation parameters are invalid");
 717  
 718      boost::container::static_vector<vk::Image, 3> raw_images;
 719  
 720      vk::ImageCreateFlags flags{};
 721      if (texture_type == VideoCore::TextureType::CubeMap) {
 722          flags |= vk::ImageCreateFlagBits::eCubeCompatible;
 723      }
 724      if (is_mutable) {
 725          flags |= vk::ImageCreateFlagBits::eMutableFormat;
 726      }
 727  
 728      const bool need_format_list = is_mutable && instance->IsImageFormatListSupported();
 729      handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
 730                              flags, traits.aspect, need_format_list, DebugName(false));
 731      raw_images.emplace_back(handles[0].image);
 732  
 733      if (res_scale != 1) {
 734          handles[1] =
 735              MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format,
 736                         traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
 737          raw_images.emplace_back(handles[1].image);
 738      }
 739  
 740      runtime->renderpass_cache.EndRendering();
 741      scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
 742          const auto barriers = MakeInitBarriers(aspect, raw_images);
 743          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
 744                                 vk::PipelineStageFlagBits::eTopOfPipe,
 745                                 vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
 746      });
 747  }
 748  
 749  Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface,
 750                   const VideoCore::Material* mat)
 751      : SurfaceBase{surface}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
 752        scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(mat->format)} {
 753      if (!traits.transfer_support) {
 754          return;
 755      }
 756  
 757      const bool has_normal = mat && mat->Map(MapType::Normal);
 758      const vk::Format format = traits.native;
 759  
 760      boost::container::static_vector<vk::Image, 2> raw_images;
 761  
 762      vk::ImageCreateFlags flags{};
 763      if (texture_type == VideoCore::TextureType::CubeMap) {
 764          flags |= vk::ImageCreateFlagBits::eCubeCompatible;
 765      }
 766  
 767      const std::string debug_name = DebugName(false, true);
 768      handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
 769                              traits.usage, flags, traits.aspect, false, debug_name);
 770      raw_images.emplace_back(handles[0].image);
 771  
 772      if (res_scale != 1) {
 773          handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type,
 774                                  vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect,
 775                                  false, debug_name);
 776          raw_images.emplace_back(handles[1].image);
 777      }
 778      if (has_normal) {
 779          handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
 780                                  traits.usage, flags, traits.aspect, false, debug_name);
 781          raw_images.emplace_back(handles[2].image);
 782      }
 783  
 784      runtime->renderpass_cache.EndRendering();
 785      scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
 786          const auto barriers = MakeInitBarriers(aspect, raw_images);
 787          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
 788                                 vk::PipelineStageFlagBits::eTopOfPipe,
 789                                 vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
 790      });
 791  
 792      custom_format = mat->format;
 793      material = mat;
 794  }
 795  
 796  Surface::~Surface() {
 797      if (!handles[0].image_view) {
 798          return;
 799      }
 800      for (const auto& [alloc, image, image_view] : handles) {
 801          if (image_view) {
 802              runtime->FreeDescriptorSetsWithImage(*image_view);
 803          }
 804          if (image) {
 805              vmaDestroyImage(instance->GetAllocator(), image, alloc);
 806          }
 807      }
 808      if (copy_handle.image_view) {
 809          vmaDestroyImage(instance->GetAllocator(), copy_handle.image, copy_handle.alloc);
 810      }
 811  }
 812  
 813  void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
 814                       const VideoCore::StagingData& staging) {
 815      runtime->renderpass_cache.EndRendering();
 816  
 817      const RecordParams params = {
 818          .aspect = Aspect(),
 819          .pipeline_flags = PipelineStageFlags(),
 820          .src_access = AccessFlags(),
 821          .src_image = Image(0),
 822      };
 823  
 824      scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params,
 825                         staging, upload](vk::CommandBuffer cmdbuf) {
 826          boost::container::static_vector<vk::BufferImageCopy, 2> buffer_image_copies;
 827  
 828          const auto rect = upload.texture_rect;
 829          buffer_image_copies.emplace_back(vk::BufferImageCopy{
 830              .bufferOffset = upload.buffer_offset,
 831              .bufferRowLength = rect.GetWidth(),
 832              .bufferImageHeight = rect.GetHeight(),
 833              .imageSubresource{
 834                  .aspectMask = params.aspect,
 835                  .mipLevel = upload.texture_level,
 836                  .baseArrayLayer = 0,
 837                  .layerCount = 1,
 838              },
 839              .imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
 840              .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
 841          });
 842  
 843          if (params.aspect & vk::ImageAspectFlagBits::eStencil) {
 844              buffer_image_copies[0].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;
 845  
 846              vk::BufferImageCopy& stencil_copy =
 847                  buffer_image_copies.emplace_back(buffer_image_copies[0]);
 848              stencil_copy = buffer_image_copies[0];
 849              stencil_copy.bufferOffset += UnpackDepthStencil(staging, format);
 850              stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;
 851          }
 852  
 853          const vk::ImageMemoryBarrier read_barrier = {
 854              .srcAccessMask = params.src_access,
 855              .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 856              .oldLayout = vk::ImageLayout::eGeneral,
 857              .newLayout = vk::ImageLayout::eTransferDstOptimal,
 858              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 859              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 860              .image = params.src_image,
 861              .subresourceRange = MakeSubresourceRange(params.aspect, upload.texture_level),
 862          };
 863          const vk::ImageMemoryBarrier write_barrier = {
 864              .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 865              .dstAccessMask = params.src_access,
 866              .oldLayout = vk::ImageLayout::eTransferDstOptimal,
 867              .newLayout = vk::ImageLayout::eGeneral,
 868              .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 869              .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 870              .image = params.src_image,
 871              .subresourceRange = MakeSubresourceRange(params.aspect, upload.texture_level),
 872          };
 873  
 874          cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
 875                                 vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
 876  
 877          cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal,
 878                                   buffer_image_copies);
 879  
 880          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
 881                                 vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
 882      });
 883  
 884      runtime->upload_buffer.Commit(staging.size);
 885  
 886      if (res_scale != 1) {
 887          const VideoCore::TextureBlit blit = {
 888              .src_level = upload.texture_level,
 889              .dst_level = upload.texture_level,
 890              .src_rect = upload.texture_rect,
 891              .dst_rect = upload.texture_rect * res_scale,
 892          };
 893  
 894          BlitScale(blit, true);
 895      }
 896  }
 897  
 898  void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
 899      const u32 width = material->width;
 900      const u32 height = material->height;
 901      const auto color = material->textures[0];
 902      const Common::Rectangle rect{0U, height, width, 0U};
 903  
 904      const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) {
 905          const u64 custom_size = texture->data.size();
 906          const RecordParams params = {
 907              .aspect = vk::ImageAspectFlagBits::eColor,
 908              .pipeline_flags = PipelineStageFlags(),
 909              .src_access = AccessFlags(),
 910              .src_image = Image(index),
 911          };
 912  
 913          const auto [data, offset, invalidate] = runtime->upload_buffer.Map(custom_size, 0);
 914          std::memcpy(data, texture->data.data(), custom_size);
 915          runtime->upload_buffer.Commit(custom_size);
 916  
 917          scheduler->Record([buffer = runtime->upload_buffer.Handle(), level, params, rect,
 918                             offset = offset](vk::CommandBuffer cmdbuf) {
 919              const vk::BufferImageCopy buffer_image_copy = {
 920                  .bufferOffset = offset,
 921                  .bufferRowLength = 0,
 922                  .bufferImageHeight = rect.GetHeight(),
 923                  .imageSubresource{
 924                      .aspectMask = params.aspect,
 925                      .mipLevel = level,
 926                      .baseArrayLayer = 0,
 927                      .layerCount = 1,
 928                  },
 929                  .imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
 930                  .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
 931              };
 932  
 933              const vk::ImageMemoryBarrier read_barrier = {
 934                  .srcAccessMask = params.src_access,
 935                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
 936                  .oldLayout = vk::ImageLayout::eGeneral,
 937                  .newLayout = vk::ImageLayout::eTransferDstOptimal,
 938                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 939                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 940                  .image = params.src_image,
 941                  .subresourceRange = MakeSubresourceRange(params.aspect, level),
 942              };
 943              const vk::ImageMemoryBarrier write_barrier = {
 944                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
 945                  .dstAccessMask = params.src_access,
 946                  .oldLayout = vk::ImageLayout::eTransferDstOptimal,
 947                  .newLayout = vk::ImageLayout::eGeneral,
 948                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 949                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
 950                  .image = params.src_image,
 951                  .subresourceRange = MakeSubresourceRange(params.aspect, level),
 952              };
 953  
 954              cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
 955                                     vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
 956  
 957              cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal,
 958                                       buffer_image_copy);
 959  
 960              cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
 961                                     vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
 962          });
 963      };
 964  
 965      upload(0, color);
 966  
 967      for (u32 i = 1; i < VideoCore::MAX_MAPS; i++) {
 968          const auto texture = material->textures[i];
 969          if (!texture) {
 970              continue;
 971          }
 972          upload(i + 1, texture);
 973      }
 974  }
 975  
 976  void Surface::Download(const VideoCore::BufferTextureCopy& download,
 977                         const VideoCore::StagingData& staging) {
 978      SCOPE_EXIT({
 979          scheduler->Finish();
 980          runtime->download_buffer.Commit(staging.size);
 981      });
 982  
 983      runtime->renderpass_cache.EndRendering();
 984  
 985      if (pixel_format == PixelFormat::D24S8) {
 986          runtime->blit_helper.DepthToBuffer(*this, runtime->download_buffer.Handle(), download);
 987          return;
 988      }
 989  
 990      if (res_scale != 1) {
 991          const VideoCore::TextureBlit blit = {
 992              .src_level = download.texture_level,
 993              .dst_level = download.texture_level,
 994              .src_rect = download.texture_rect * res_scale,
 995              .dst_rect = download.texture_rect,
 996          };
 997  
 998          BlitScale(blit, false);
 999      }
1000  
1001      const RecordParams params = {
1002          .aspect = Aspect(),
1003          .pipeline_flags = PipelineStageFlags(),
1004          .src_access = AccessFlags(),
1005          .src_image = Image(0),
1006      };
1007  
1008      scheduler->Record(
1009          [buffer = runtime->download_buffer.Handle(), params, download](vk::CommandBuffer cmdbuf) {
1010              const auto rect = download.texture_rect;
1011              const vk::BufferImageCopy buffer_image_copy = {
1012                  .bufferOffset = download.buffer_offset,
1013                  .bufferRowLength = rect.GetWidth(),
1014                  .bufferImageHeight = rect.GetHeight(),
1015                  .imageSubresource{
1016                      .aspectMask = params.aspect,
1017                      .mipLevel = download.texture_level,
1018                      .baseArrayLayer = 0,
1019                      .layerCount = 1,
1020                  },
1021                  .imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
1022                  .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
1023              };
1024  
1025              const vk::ImageMemoryBarrier read_barrier = {
1026                  .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
1027                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
1028                  .oldLayout = vk::ImageLayout::eGeneral,
1029                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
1030                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1031                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1032                  .image = params.src_image,
1033                  .subresourceRange = MakeSubresourceRange(params.aspect, download.texture_level),
1034              };
1035              const vk::ImageMemoryBarrier image_write_barrier = {
1036                  .srcAccessMask = vk::AccessFlagBits::eNone,
1037                  .dstAccessMask = vk::AccessFlagBits::eMemoryWrite,
1038                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
1039                  .newLayout = vk::ImageLayout::eGeneral,
1040                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1041                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1042                  .image = params.src_image,
1043                  .subresourceRange = MakeSubresourceRange(params.aspect, download.texture_level),
1044              };
1045              const vk::MemoryBarrier memory_write_barrier = {
1046                  .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
1047                  .dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite,
1048              };
1049  
1050              cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
1051                                     vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
1052  
1053              cmdbuf.copyImageToBuffer(params.src_image, vk::ImageLayout::eTransferSrcOptimal, buffer,
1054                                       buffer_image_copy);
1055  
1056              cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
1057                                     vk::DependencyFlagBits::eByRegion, memory_write_barrier, {},
1058                                     image_write_barrier);
1059          });
1060  }
1061  
1062  void Surface::ScaleUp(u32 new_scale) {
1063      if (res_scale == new_scale || new_scale == 1) {
1064          return;
1065      }
1066  
1067      res_scale = new_scale;
1068  
1069      const bool is_mutable = pixel_format == VideoCore::PixelFormat::RGBA8;
1070  
1071      vk::ImageCreateFlags flags{};
1072      if (texture_type == VideoCore::TextureType::CubeMap) {
1073          flags |= vk::ImageCreateFlagBits::eCubeCompatible;
1074      }
1075      if (is_mutable) {
1076          flags |= vk::ImageCreateFlagBits::eMutableFormat;
1077      }
1078  
1079      handles[1] =
1080          MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type,
1081                     traits.native, traits.usage, flags, traits.aspect, false, DebugName(true));
1082  
1083      runtime->renderpass_cache.EndRendering();
1084      scheduler->Record(
1085          [raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
1086              const auto barriers = MakeInitBarriers(aspect, raw_images);
1087              cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
1088                                     vk::PipelineStageFlagBits::eTopOfPipe,
1089                                     vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
1090          });
1091      LOG_INFO(HW_GPU, "Surface scale up!");
1092      for (u32 level = 0; level < levels; level++) {
1093          const VideoCore::TextureBlit blit = {
1094              .src_level = level,
1095              .dst_level = level,
1096              .src_rect = GetRect(level),
1097              .dst_rect = GetScaledRect(level),
1098          };
1099          BlitScale(blit, true);
1100      }
1101  }
1102  
1103  u32 Surface::GetInternalBytesPerPixel() const {
1104      // Request 5 bytes for D24S8 as well because we can use the
1105      // extra space when deinterleaving the data during upload
1106      if (traits.native == vk::Format::eD24UnormS8Uint) {
1107          return 5;
1108      }
1109  
1110      return vk::blockSize(traits.native);
1111  }
1112  
1113  vk::AccessFlags Surface::AccessFlags() const noexcept {
1114      const bool is_color = static_cast<bool>(Aspect() & vk::ImageAspectFlagBits::eColor);
1115      const vk::AccessFlags attachment_flags =
1116          is_color
1117              ? vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite
1118              : vk::AccessFlagBits::eDepthStencilAttachmentRead |
1119                    vk::AccessFlagBits::eDepthStencilAttachmentWrite;
1120  
1121      return vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eTransferRead |
1122             vk::AccessFlagBits::eTransferWrite |
1123             (is_framebuffer ? attachment_flags : vk::AccessFlagBits::eNone) |
1124             (is_storage ? vk::AccessFlagBits::eShaderWrite : vk::AccessFlagBits::eNone);
1125  }
1126  
1127  vk::PipelineStageFlags Surface::PipelineStageFlags() const noexcept {
1128      const bool is_color = static_cast<bool>(Aspect() & vk::ImageAspectFlagBits::eColor);
1129      const vk::PipelineStageFlags attachment_flags =
1130          is_color ? vk::PipelineStageFlagBits::eColorAttachmentOutput
1131                   : vk::PipelineStageFlagBits::eEarlyFragmentTests |
1132                         vk::PipelineStageFlagBits::eLateFragmentTests;
1133  
1134      return vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eFragmentShader |
1135             (is_framebuffer ? attachment_flags : vk::PipelineStageFlagBits::eNone) |
1136             (is_storage ? vk::PipelineStageFlagBits::eComputeShader
1137                         : vk::PipelineStageFlagBits::eNone);
1138  }
1139  
1140  vk::Image Surface::Image(u32 index) const noexcept {
1141      const vk::Image image = handles[index].image;
1142      if (!image) {
1143          return handles[0].image;
1144      }
1145      return image;
1146  }
1147  
1148  vk::ImageView Surface::CopyImageView() noexcept {
1149      vk::ImageLayout copy_layout = vk::ImageLayout::eGeneral;
1150      if (!copy_handle.image) {
1151          vk::ImageCreateFlags flags{};
1152          if (texture_type == VideoCore::TextureType::CubeMap) {
1153              flags |= vk::ImageCreateFlagBits::eCubeCompatible;
1154          }
1155          copy_handle =
1156              MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type,
1157                         traits.native, traits.usage, flags, traits.aspect, false);
1158          copy_layout = vk::ImageLayout::eUndefined;
1159      }
1160  
1161      runtime->renderpass_cache.EndRendering();
1162  
1163      const RecordParams params = {
1164          .aspect = Aspect(),
1165          .pipeline_flags = PipelineStageFlags(),
1166          .src_access = AccessFlags(),
1167          .src_image = Image(),
1168          .dst_image = copy_handle.image,
1169      };
1170  
1171      scheduler->Record([params, copy_layout, levels = this->levels, width = GetScaledWidth(),
1172                         height = GetScaledHeight()](vk::CommandBuffer cmdbuf) {
1173          std::array pre_barriers = {
1174              vk::ImageMemoryBarrier{
1175                  .srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
1176                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
1177                  .oldLayout = vk::ImageLayout::eGeneral,
1178                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
1179                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1180                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1181                  .image = params.src_image,
1182                  .subresourceRange = MakeSubresourceRange(params.aspect, 0, levels),
1183              },
1184              vk::ImageMemoryBarrier{
1185                  .srcAccessMask = vk::AccessFlagBits::eShaderRead,
1186                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
1187                  .oldLayout = copy_layout,
1188                  .newLayout = vk::ImageLayout::eTransferDstOptimal,
1189                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1190                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1191                  .image = params.dst_image,
1192                  .subresourceRange = MakeSubresourceRange(params.aspect, 0, levels),
1193              },
1194          };
1195          std::array post_barriers = {
1196              vk::ImageMemoryBarrier{
1197                  .srcAccessMask = vk::AccessFlagBits::eTransferRead,
1198                  .dstAccessMask = vk::AccessFlagBits::eColorAttachmentWrite,
1199                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
1200                  .newLayout = vk::ImageLayout::eGeneral,
1201                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1202                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1203                  .image = params.src_image,
1204                  .subresourceRange = MakeSubresourceRange(params.aspect, 0, levels),
1205              },
1206              vk::ImageMemoryBarrier{
1207                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
1208                  .dstAccessMask = vk::AccessFlagBits::eShaderRead,
1209                  .oldLayout = vk::ImageLayout::eTransferDstOptimal,
1210                  .newLayout = vk::ImageLayout::eGeneral,
1211                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1212                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1213                  .image = params.dst_image,
1214                  .subresourceRange = MakeSubresourceRange(params.aspect, 0, levels),
1215              },
1216          };
1217  
1218          boost::container::small_vector<vk::ImageCopy, 3> image_copies;
1219          for (u32 level = 0; level < levels; level++) {
1220              image_copies.push_back(vk::ImageCopy{
1221                  .srcSubresource{
1222                      .aspectMask = vk::ImageAspectFlagBits::eColor,
1223                      .mipLevel = level,
1224                      .baseArrayLayer = 0,
1225                      .layerCount = 1,
1226                  },
1227                  .srcOffset = {0, 0, 0},
1228                  .dstSubresource{
1229                      .aspectMask = vk::ImageAspectFlagBits::eColor,
1230                      .mipLevel = level,
1231                      .baseArrayLayer = 0,
1232                      .layerCount = 1,
1233                  },
1234                  .dstOffset = {0, 0, 0},
1235                  .extent = {width >> level, height >> level, 1},
1236              });
1237          }
1238  
1239          cmdbuf.pipelineBarrier(params.pipeline_flags, vk::PipelineStageFlagBits::eTransfer,
1240                                 vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
1241  
1242          cmdbuf.copyImage(params.src_image, vk::ImageLayout::eTransferSrcOptimal, params.dst_image,
1243                           vk::ImageLayout::eTransferDstOptimal, image_copies);
1244  
1245          cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
1246                                 vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers);
1247      });
1248  
1249      return copy_handle.image_view.get();
1250  }
1251  
1252  vk::ImageView Surface::ImageView(u32 index) const noexcept {
1253      const auto& image_view = handles[index].image_view.get();
1254      if (!image_view) {
1255          return handles[0].image_view.get();
1256      }
1257      return image_view;
1258  }
1259  
1260  vk::ImageView Surface::FramebufferView() noexcept {
1261      is_framebuffer = true;
1262      return ImageView();
1263  }
1264  
1265  vk::ImageView Surface::DepthView() noexcept {
1266      if (depth_view) {
1267          return depth_view.get();
1268      }
1269  
1270      const vk::ImageViewCreateInfo view_info = {
1271          .image = Image(),
1272          .viewType = vk::ImageViewType::e2D,
1273          .format = instance->GetTraits(pixel_format).native,
1274          .subresourceRange{
1275              .aspectMask = vk::ImageAspectFlagBits::eDepth,
1276              .baseMipLevel = 0,
1277              .levelCount = VK_REMAINING_MIP_LEVELS,
1278              .baseArrayLayer = 0,
1279              .layerCount = VK_REMAINING_ARRAY_LAYERS,
1280          },
1281      };
1282  
1283      depth_view = instance->GetDevice().createImageViewUnique(view_info);
1284      return depth_view.get();
1285  }
1286  
1287  vk::ImageView Surface::StencilView() noexcept {
1288      if (stencil_view) {
1289          return stencil_view.get();
1290      }
1291  
1292      const vk::ImageViewCreateInfo view_info = {
1293          .image = Image(),
1294          .viewType = vk::ImageViewType::e2D,
1295          .format = instance->GetTraits(pixel_format).native,
1296          .subresourceRange{
1297              .aspectMask = vk::ImageAspectFlagBits::eStencil,
1298              .baseMipLevel = 0,
1299              .levelCount = VK_REMAINING_MIP_LEVELS,
1300              .baseArrayLayer = 0,
1301              .layerCount = VK_REMAINING_ARRAY_LAYERS,
1302          },
1303      };
1304  
1305      stencil_view = instance->GetDevice().createImageViewUnique(view_info);
1306      return stencil_view.get();
1307  }
1308  
1309  vk::ImageView Surface::StorageView() noexcept {
1310      if (storage_view) {
1311          return storage_view.get();
1312      }
1313  
1314      if (pixel_format != VideoCore::PixelFormat::RGBA8) {
1315          LOG_WARNING(Render_Vulkan,
1316                      "Attempted to retrieve storage view from unsupported surface with format {}",
1317                      VideoCore::PixelFormatAsString(pixel_format));
1318          return ImageView();
1319      }
1320  
1321      is_storage = true;
1322  
1323      const vk::ImageViewCreateInfo storage_view_info = {
1324          .image = Image(),
1325          .viewType = vk::ImageViewType::e2D,
1326          .format = vk::Format::eR32Uint,
1327          .subresourceRange{
1328              .aspectMask = vk::ImageAspectFlagBits::eColor,
1329              .baseMipLevel = 0,
1330              .levelCount = VK_REMAINING_MIP_LEVELS,
1331              .baseArrayLayer = 0,
1332              .layerCount = VK_REMAINING_ARRAY_LAYERS,
1333          },
1334      };
1335      storage_view = instance->GetDevice().createImageViewUnique(storage_view_info);
1336      return storage_view.get();
1337  }
1338  
1339  vk::Framebuffer Surface::Framebuffer() noexcept {
1340      const u32 index = res_scale == 1 ? 0u : 1u;
1341      if (framebuffers[index]) {
1342          return framebuffers[index].get();
1343      }
1344  
1345      const bool is_depth = type == SurfaceType::Depth || type == SurfaceType::DepthStencil;
1346      const auto color_format = is_depth ? PixelFormat::Invalid : pixel_format;
1347      const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid;
1348      const auto render_pass =
1349          runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
1350      const auto attachments = std::array{ImageView()};
1351      framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
1352                                            GetScaledHeight(), attachments);
1353      return framebuffers[index].get();
1354  }
1355  
1356  void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
1357      const FormatTraits& depth_traits = instance->GetTraits(pixel_format);
1358      const bool is_depth_stencil = pixel_format == PixelFormat::D24S8;
1359      if (is_depth_stencil && !depth_traits.blit_support) {
1360          LOG_WARNING(Render_Vulkan, "Depth scale unsupported by hardware");
1361          return;
1362      }
1363  
1364      scheduler->Record([src_image = Image(!up_scale), aspect = Aspect(),
1365                         filter = MakeFilter(pixel_format), dst_image = Image(up_scale),
1366                         blit](vk::CommandBuffer render_cmdbuf) {
1367          const std::array source_offsets = {
1368              vk::Offset3D{static_cast<s32>(blit.src_rect.left),
1369                           static_cast<s32>(blit.src_rect.bottom), 0},
1370              vk::Offset3D{static_cast<s32>(blit.src_rect.right), static_cast<s32>(blit.src_rect.top),
1371                           1},
1372          };
1373  
1374          const std::array dest_offsets = {
1375              vk::Offset3D{static_cast<s32>(blit.dst_rect.left),
1376                           static_cast<s32>(blit.dst_rect.bottom), 0},
1377              vk::Offset3D{static_cast<s32>(blit.dst_rect.right), static_cast<s32>(blit.dst_rect.top),
1378                           1},
1379          };
1380  
1381          const vk::ImageBlit blit_area = {
1382              .srcSubresource{
1383                  .aspectMask = aspect,
1384                  .mipLevel = blit.src_level,
1385                  .baseArrayLayer = blit.src_layer,
1386                  .layerCount = 1,
1387              },
1388              .srcOffsets = source_offsets,
1389              .dstSubresource{
1390                  .aspectMask = aspect,
1391                  .mipLevel = blit.dst_level,
1392                  .baseArrayLayer = blit.dst_layer,
1393                  .layerCount = 1,
1394              },
1395              .dstOffsets = dest_offsets,
1396          };
1397  
1398          const std::array read_barriers = {
1399              vk::ImageMemoryBarrier{
1400                  .srcAccessMask = vk::AccessFlagBits::eMemoryWrite,
1401                  .dstAccessMask = vk::AccessFlagBits::eTransferRead,
1402                  .oldLayout = vk::ImageLayout::eGeneral,
1403                  .newLayout = vk::ImageLayout::eTransferSrcOptimal,
1404                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1405                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1406                  .image = src_image,
1407                  .subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
1408              },
1409              vk::ImageMemoryBarrier{
1410                  .srcAccessMask = vk::AccessFlagBits::eShaderRead |
1411                                   vk::AccessFlagBits::eDepthStencilAttachmentRead |
1412                                   vk::AccessFlagBits::eColorAttachmentRead |
1413                                   vk::AccessFlagBits::eTransferRead,
1414                  .dstAccessMask = vk::AccessFlagBits::eTransferWrite,
1415                  .oldLayout = vk::ImageLayout::eGeneral,
1416                  .newLayout = vk::ImageLayout::eTransferDstOptimal,
1417                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1418                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1419                  .image = dst_image,
1420                  .subresourceRange = MakeSubresourceRange(aspect, blit.dst_level),
1421              },
1422          };
1423          const std::array write_barriers = {
1424              vk::ImageMemoryBarrier{
1425                  .srcAccessMask = vk::AccessFlagBits::eNone,
1426                  .dstAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead,
1427                  .oldLayout = vk::ImageLayout::eTransferSrcOptimal,
1428                  .newLayout = vk::ImageLayout::eGeneral,
1429                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1430                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1431                  .image = src_image,
1432                  .subresourceRange = MakeSubresourceRange(aspect, blit.src_level),
1433              },
1434              vk::ImageMemoryBarrier{
1435                  .srcAccessMask = vk::AccessFlagBits::eTransferWrite,
1436                  .dstAccessMask = vk::AccessFlagBits::eMemoryWrite | vk::AccessFlagBits::eMemoryRead,
1437                  .oldLayout = vk::ImageLayout::eTransferDstOptimal,
1438                  .newLayout = vk::ImageLayout::eGeneral,
1439                  .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1440                  .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
1441                  .image = dst_image,
1442                  .subresourceRange = MakeSubresourceRange(aspect, blit.dst_level),
1443              },
1444          };
1445  
1446          render_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands,
1447                                        vk::PipelineStageFlagBits::eTransfer,
1448                                        vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers);
1449  
1450          render_cmdbuf.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image,
1451                                  vk::ImageLayout::eTransferDstOptimal, blit_area, filter);
1452  
1453          render_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
1454                                        vk::PipelineStageFlagBits::eAllCommands,
1455                                        vk::DependencyFlagBits::eByRegion, {}, {}, write_barriers);
1456      });
1457  }
1458  
1459  Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferParams& params,
1460                           Surface* color, Surface* depth)
1461      : VideoCore::FramebufferParams{params}, res_scale{color ? color->res_scale
1462                                                              : (depth ? depth->res_scale : 1u)} {
1463      auto& renderpass_cache = runtime.GetRenderpassCache();
1464      if (shadow_rendering && !color) {
1465          return;
1466      }
1467  
1468      width = height = std::numeric_limits<u32>::max();
1469  
1470      const auto prepare = [&](u32 index, Surface* surface) {
1471          const VideoCore::Extent extent = surface->RealExtent();
1472          width = std::min(width, extent.width);
1473          height = std::min(height, extent.height);
1474          if (!shadow_rendering) {
1475              formats[index] = surface->pixel_format;
1476          }
1477          images[index] = surface->Image();
1478          aspects[index] = surface->Aspect();
1479          image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
1480      };
1481  
1482      boost::container::static_vector<vk::ImageView, 2> attachments;
1483  
1484      if (color) {
1485          prepare(0, color);
1486          attachments.emplace_back(image_views[0]);
1487      }
1488  
1489      if (depth) {
1490          prepare(1, depth);
1491          attachments.emplace_back(image_views[1]);
1492      }
1493  
1494      const vk::Device device = runtime.GetInstance().GetDevice();
1495      if (shadow_rendering) {
1496          render_pass =
1497              renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
1498          framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
1499                                        color->GetScaledHeight(), {});
1500      } else {
1501          render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
1502          framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);
1503      }
1504  }
1505  
1506  Framebuffer::~Framebuffer() = default;
1507  
1508  Sampler::Sampler(TextureRuntime& runtime, const VideoCore::SamplerParams& params) {
1509      using TextureConfig = VideoCore::SamplerParams::TextureConfig;
1510  
1511      const Instance& instance = runtime.GetInstance();
1512      const vk::PhysicalDeviceProperties properties = instance.GetPhysicalDevice().getProperties();
1513      const bool use_border_color =
1514          instance.IsCustomBorderColorSupported() && (params.wrap_s == TextureConfig::ClampToBorder ||
1515                                                      params.wrap_t == TextureConfig::ClampToBorder);
1516  
1517      const Common::Vec4f color = PicaToVK::ColorRGBA8(params.border_color);
1518      const vk::SamplerCustomBorderColorCreateInfoEXT border_color_info = {
1519          .customBorderColor = MakeClearColorValue(color),
1520          .format = vk::Format::eUndefined,
1521      };
1522  
1523      const vk::Filter mag_filter = PicaToVK::TextureFilterMode(params.mag_filter);
1524      const vk::Filter min_filter = PicaToVK::TextureFilterMode(params.min_filter);
1525      const vk::SamplerMipmapMode mipmap_mode = PicaToVK::TextureMipFilterMode(params.mip_filter);
1526      const vk::SamplerAddressMode wrap_u = PicaToVK::WrapMode(params.wrap_s);
1527      const vk::SamplerAddressMode wrap_v = PicaToVK::WrapMode(params.wrap_t);
1528      const float lod_min = static_cast<float>(params.lod_min);
1529      const float lod_max = static_cast<float>(params.lod_max);
1530  
1531      const vk::SamplerCreateInfo sampler_info = {
1532          .pNext = use_border_color ? &border_color_info : nullptr,
1533          .magFilter = mag_filter,
1534          .minFilter = min_filter,
1535          .mipmapMode = mipmap_mode,
1536          .addressModeU = wrap_u,
1537          .addressModeV = wrap_v,
1538          .mipLodBias = 0,
1539          .anisotropyEnable = instance.IsAnisotropicFilteringSupported(),
1540          .maxAnisotropy = properties.limits.maxSamplerAnisotropy,
1541          .compareEnable = false,
1542          .compareOp = vk::CompareOp::eAlways,
1543          .minLod = lod_min,
1544          .maxLod = lod_max,
1545          .borderColor =
1546              use_border_color ? vk::BorderColor::eFloatCustomEXT : vk::BorderColor::eIntOpaqueBlack,
1547          .unnormalizedCoordinates = false,
1548      };
1549      sampler = instance.GetDevice().createSamplerUnique(sampler_info);
1550  }
1551  
1552  Sampler::~Sampler() = default;
1553  
1554  DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string_view label)
1555      : scheduler{runtime.GetScheduler()}, has_debug_tool{
1556                                               runtime.GetInstance().HasDebuggingToolAttached()} {
1557      if (!has_debug_tool) {
1558          return;
1559      }
1560      scheduler.Record([color, label = std::string(label)](vk::CommandBuffer cmdbuf) {
1561          const vk::DebugUtilsLabelEXT debug_label = {
1562              .pLabelName = label.data(),
1563              .color = std::array{color[0], color[1], color[2], color[3]},
1564          };
1565          cmdbuf.beginDebugUtilsLabelEXT(debug_label);
1566      });
1567  }
1568  
1569  DebugScope::~DebugScope() {
1570      if (!has_debug_tool) {
1571          return;
1572      }
1573      scheduler.Record([](vk::CommandBuffer cmdbuf) { cmdbuf.endDebugUtilsLabelEXT(); });
1574  }
1575  
1576  } // namespace Vulkan