/**
 ******************************************************************************
 * Xenia : Xbox 360 Emulator Research Project                                 *
 ******************************************************************************
 * Copyright 2022 Ben Vanik. All rights reserved.                             *
 * Released under the BSD license - see LICENSE in the root for more details. *
 ******************************************************************************
 */

#include "xenia/gpu/vulkan/vulkan_texture_cache.h"

#include <algorithm>
#include <array>
#include <cstddef>
#include <utility>

#include "xenia/base/assert.h"
#include "xenia/base/cvar.h"
#include "xenia/base/logging.h"
#include "xenia/base/math.h"
#include "xenia/base/profiling.h"
#include "xenia/gpu/gpu_flags.h"
#include "xenia/gpu/texture_info.h"
#include "xenia/gpu/texture_util.h"
#include "xenia/gpu/vulkan/deferred_command_buffer.h"
#include "xenia/gpu/vulkan/vulkan_command_processor.h"
#include "xenia/ui/vulkan/ui_samplers.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_util.h"

DECLARE_bool(tiled_shared_memory);

namespace xe {
namespace gpu {
namespace vulkan {

// Generated with `xb buildshaders`.
namespace shaders {
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_128bpb_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_128bpb_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_16bpb_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_16bpb_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_32bpb_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_32bpb_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_64bpb_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_64bpb_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_8bpb_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_8bpb_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_bgrg8_rgb8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_ctx1_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_unorm_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_depth_unorm_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxn_rg8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt1_rgba8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3_rgba8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3a_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt3aas1111_argb4_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt5_rgba8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_dxt5a_r8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_gbgr8_rgb8_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_snorm_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r10g11b11_rgba16_snorm_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_snorm_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r11g11b10_rgba16_snorm_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_snorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_snorm_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_unorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r16_unorm_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r4g4b4a4_a4r4g4b4_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r4g4b4a4_a4r4g4b4_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b5a1_b5g5r5a1_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b5a1_b5g5r5a1_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g6b5_b5g6r5_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_r5g6b5_b5g6r5_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_snorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_snorm_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_unorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rg16_unorm_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_snorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_snorm_float_scaled_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_unorm_float_cs.h"
#include "xenia/gpu/shaders/bytecode/vulkan_spirv/texture_load_rgba16_unorm_float_scaled_cs.h"
}  // namespace shaders

static_assert(VK_FORMAT_UNDEFINED == VkFormat(0),
              "Assuming that skipping a VkFormat in an initializer results in "
              "VK_FORMAT_UNDEFINED");
constexpr VulkanTextureCache::HostFormatPair
    VulkanTextureCache::kBestHostFormats[64] = {
        // k_1_REVERSE
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_1
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_8
        {{kLoadShaderIndex8bpb, VK_FORMAT_R8_UNORM},
         {kLoadShaderIndex8bpb, VK_FORMAT_R8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_1_5_5_5
        // Red and blue swapped in the load shader for simplicity.
        {{kLoadShaderIndexR5G5B5A1ToB5G5R5A1, VK_FORMAT_A1R5G5B5_UNORM_PACK16},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_5_6_5
        // Red and blue swapped in the load shader for simplicity.
        {{kLoadShaderIndexR5G6B5ToB5G6R5, VK_FORMAT_R5G6B5_UNORM_PACK16},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_6_5_5
        // On the host, green bits in blue, blue bits in green.
        {{kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle,
          VK_FORMAT_R5G6B5_UNORM_PACK16},
         {kLoadShaderIndexUnknown},
         XE_GPU_MAKE_TEXTURE_SWIZZLE(R, B, G, G)},
        // k_8_8_8_8
        {{kLoadShaderIndex32bpb, VK_FORMAT_R8G8B8A8_UNORM},
         {kLoadShaderIndex32bpb, VK_FORMAT_R8G8B8A8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_2_10_10_10
        // VK_FORMAT_A2B10G10R10_SNORM_PACK32 is optional.
        {{kLoadShaderIndex32bpb, VK_FORMAT_A2B10G10R10_UNORM_PACK32},
         {kLoadShaderIndex32bpb, VK_FORMAT_A2B10G10R10_SNORM_PACK32},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_8_A
        {{kLoadShaderIndex8bpb, VK_FORMAT_R8_UNORM},
         {kLoadShaderIndex8bpb, VK_FORMAT_R8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_8_B
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_8_8
        {{kLoadShaderIndex16bpb, VK_FORMAT_R8G8_UNORM},
         {kLoadShaderIndex16bpb, VK_FORMAT_R8G8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG,
         true},
        // k_Cr_Y1_Cb_Y0_REP
        // VK_FORMAT_G8B8G8R8_422_UNORM (added in
        // VK_KHR_sampler_ycbcr_conversion and promoted to Vulkan 1.1) is
        // optional.
        {{kLoadShaderIndex32bpb, VK_FORMAT_G8B8G8R8_422_UNORM, true},
         {kLoadShaderIndexGBGR8ToRGB8, VK_FORMAT_R8G8B8A8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_Y1_Cr_Y0_Cb_REP
        // VK_FORMAT_B8G8R8G8_422_UNORM (added in
        // VK_KHR_sampler_ycbcr_conversion and promoted to Vulkan 1.1) is
        // optional.
        {{kLoadShaderIndex32bpb, VK_FORMAT_B8G8R8G8_422_UNORM, true},
         {kLoadShaderIndexBGRG8ToRGB8, VK_FORMAT_R8G8B8A8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_16_16_EDRAM
        // Not usable as a texture, also has -32...32 range.
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_8_8_8_8_A
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_4_4_4_4
        // Components swapped in the load shader for simplicity.
        {{kLoadShaderIndexRGBA4ToARGB4, VK_FORMAT_B4G4R4A4_UNORM_PACK16},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_10_11_11
        // TODO(Triang3l): 16_UNORM/SNORM are optional, convert to float16
        // instead.
        {{kLoadShaderIndexR11G11B10ToRGBA16, VK_FORMAT_R16G16B16A16_UNORM},
         {kLoadShaderIndexR11G11B10ToRGBA16SNorm, VK_FORMAT_R16G16B16A16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_11_11_10
        // TODO(Triang3l): 16_UNORM/SNORM are optional, convert to float16
        // instead.
        {{kLoadShaderIndexR10G11B11ToRGBA16, VK_FORMAT_R16G16B16A16_UNORM},
         {kLoadShaderIndexR10G11B11ToRGBA16SNorm, VK_FORMAT_R16G16B16A16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_DXT1
        // VK_FORMAT_BC1_RGBA_UNORM_BLOCK is optional.
        {{kLoadShaderIndex64bpb, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_DXT2_3
        // VK_FORMAT_BC2_UNORM_BLOCK is optional.
        {{kLoadShaderIndex128bpb, VK_FORMAT_BC2_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_DXT4_5
        // VK_FORMAT_BC3_UNORM_BLOCK is optional.
        {{kLoadShaderIndex128bpb, VK_FORMAT_BC3_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_16_16_16_16_EDRAM
        // Not usable as a texture, also has -32...32 range.
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_24_8
        {{kLoadShaderIndexDepthUnorm, VK_FORMAT_R32_SFLOAT},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_24_8_FLOAT
        {{kLoadShaderIndexDepthFloat, VK_FORMAT_R32_SFLOAT},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_16
        // VK_FORMAT_R16_UNORM and VK_FORMAT_R16_SNORM are optional.
        {{kLoadShaderIndex16bpb, VK_FORMAT_R16_UNORM},
         {kLoadShaderIndex16bpb, VK_FORMAT_R16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_16_16
        // VK_FORMAT_R16G16_UNORM and VK_FORMAT_R16G16_SNORM are optional.
        {{kLoadShaderIndex32bpb, VK_FORMAT_R16G16_UNORM},
         {kLoadShaderIndex32bpb, VK_FORMAT_R16G16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG,
         true},
        // k_16_16_16_16
        // VK_FORMAT_R16G16B16A16_UNORM and VK_FORMAT_R16G16B16A16_SNORM are
        // optional.
        {{kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_UNORM},
         {kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_16_EXPAND
        {{kLoadShaderIndex16bpb, VK_FORMAT_R16_SFLOAT},
         {kLoadShaderIndex16bpb, VK_FORMAT_R16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_16_16_EXPAND
        {{kLoadShaderIndex32bpb, VK_FORMAT_R16G16_SFLOAT},
         {kLoadShaderIndex32bpb, VK_FORMAT_R16G16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG,
         true},
        // k_16_16_16_16_EXPAND
        {{kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_SFLOAT},
         {kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_16_FLOAT
        {{kLoadShaderIndex16bpb, VK_FORMAT_R16_SFLOAT},
         {kLoadShaderIndex16bpb, VK_FORMAT_R16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_16_16_FLOAT
        {{kLoadShaderIndex32bpb, VK_FORMAT_R16G16_SFLOAT},
         {kLoadShaderIndex32bpb, VK_FORMAT_R16G16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG,
         true},
        // k_16_16_16_16_FLOAT
        {{kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_SFLOAT},
         {kLoadShaderIndex64bpb, VK_FORMAT_R16G16B16A16_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_32
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_32_32
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_32_32_32_32
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_32_FLOAT
        {{kLoadShaderIndex32bpb, VK_FORMAT_R32_SFLOAT},
         {kLoadShaderIndex32bpb, VK_FORMAT_R32_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR,
         true},
        // k_32_32_FLOAT
        {{kLoadShaderIndex64bpb, VK_FORMAT_R32G32_SFLOAT},
         {kLoadShaderIndex64bpb, VK_FORMAT_R32G32_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG,
         true},
        // k_32_32_32_32_FLOAT
        {{kLoadShaderIndex128bpb, VK_FORMAT_R32G32B32A32_SFLOAT},
         {kLoadShaderIndex128bpb, VK_FORMAT_R32G32B32A32_SFLOAT},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_32_AS_8
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_32_AS_8_8
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_16_MPEG
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_16_16_MPEG
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_8_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_32_AS_8_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_32_AS_8_8_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_16_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_16_MPEG_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_16_16_MPEG_INTERLACED
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_DXN
        // VK_FORMAT_BC5_UNORM_BLOCK is optional.
        {{kLoadShaderIndex128bpb, VK_FORMAT_BC5_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_8_8_8_8_AS_16_16_16_16
        {{kLoadShaderIndex32bpb, VK_FORMAT_R8G8B8A8_UNORM},
         {kLoadShaderIndex32bpb, VK_FORMAT_R8G8B8A8_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_DXT1_AS_16_16_16_16
        // VK_FORMAT_BC1_RGBA_UNORM_BLOCK is optional.
        {{kLoadShaderIndex64bpb, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_DXT2_3_AS_16_16_16_16
        // VK_FORMAT_BC2_UNORM_BLOCK is optional.
        {{kLoadShaderIndex128bpb, VK_FORMAT_BC2_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_DXT4_5_AS_16_16_16_16
        // VK_FORMAT_BC3_UNORM_BLOCK is optional.
        {{kLoadShaderIndex128bpb, VK_FORMAT_BC3_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_2_10_10_10_AS_16_16_16_16
        // VK_FORMAT_A2B10G10R10_SNORM_PACK32 is optional.
        {{kLoadShaderIndex32bpb, VK_FORMAT_A2B10G10R10_UNORM_PACK32},
         {kLoadShaderIndex32bpb, VK_FORMAT_A2B10G10R10_SNORM_PACK32},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA,
         true},
        // k_10_11_11_AS_16_16_16_16
        // TODO(Triang3l): 16_UNORM/SNORM are optional, convert to float16
        // instead.
        {{kLoadShaderIndexR11G11B10ToRGBA16, VK_FORMAT_R16G16B16A16_UNORM},
         {kLoadShaderIndexR11G11B10ToRGBA16SNorm, VK_FORMAT_R16G16B16A16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_11_11_10_AS_16_16_16_16
        // TODO(Triang3l): 16_UNORM/SNORM are optional, convert to float16
        // instead.
        {{kLoadShaderIndexR10G11B11ToRGBA16, VK_FORMAT_R16G16B16A16_UNORM},
         {kLoadShaderIndexR10G11B11ToRGBA16SNorm, VK_FORMAT_R16G16B16A16_SNORM},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_32_32_32_FLOAT
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB},
        // k_DXT3A
        {{kLoadShaderIndexDXT3A, VK_FORMAT_R8_UNORM},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_DXT5A
        // VK_FORMAT_BC4_UNORM_BLOCK is optional.
        {{kLoadShaderIndex64bpb, VK_FORMAT_BC4_UNORM_BLOCK, true},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RRRR},
        // k_CTX1
        {{kLoadShaderIndexCTX1, VK_FORMAT_R8G8_UNORM},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGGG},
        // k_DXT3A_AS_1_1_1_1
        {{kLoadShaderIndexDXT3AAs1111ToARGB4, VK_FORMAT_B4G4R4A4_UNORM_PACK16},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_8_8_8_8_GAMMA_EDRAM
        // Not usable as a texture.
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
        // k_2_10_10_10_FLOAT_EDRAM
        // Not usable as a texture.
        {{kLoadShaderIndexUnknown},
         {kLoadShaderIndexUnknown},
         xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA},
};

// Vulkan requires 2x1 (4:2:2) subsampled images to have an even width.
// Always decompressing them to RGBA8, which is required to be linear-filterable
// as UNORM and SNORM.

constexpr VulkanTextureCache::HostFormatPair
    VulkanTextureCache::kHostFormatGBGRUnaligned = {
        {kLoadShaderIndexGBGR8ToRGB8, VK_FORMAT_R8G8B8A8_UNORM, false, true},
        {kLoadShaderIndexGBGR8ToRGB8, VK_FORMAT_R8G8B8A8_SNORM, false, true},
        xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB,
        true};

constexpr VulkanTextureCache::HostFormatPair
    VulkanTextureCache::kHostFormatBGRGUnaligned = {
        {kLoadShaderIndexBGRG8ToRGB8, VK_FORMAT_R8G8B8A8_UNORM, false, true},
        {kLoadShaderIndexBGRG8ToRGB8, VK_FORMAT_R8G8B8A8_SNORM, false, true},
        xenos::XE_GPU_TEXTURE_SWIZZLE_RGBB,
        true};

VulkanTextureCache::~VulkanTextureCache() {
  const ui::vulkan::VulkanDevice* const vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();

  for (const std::pair<const SamplerParameters, Sampler>& sampler_pair :
       samplers_) {
    dfn.vkDestroySampler(device, sampler_pair.second.sampler, nullptr);
  }
  samplers_.clear();
  COUNT_profile_set("gpu/texture_cache/vulkan/samplers", 0);
  sampler_used_last_ = nullptr;
  sampler_used_first_ = nullptr;

  if (null_image_view_3d_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImageView(device, null_image_view_3d_, nullptr);
  }
  if (null_image_view_cube_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImageView(device, null_image_view_cube_, nullptr);
  }
  if (null_image_view_2d_array_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImageView(device, null_image_view_2d_array_, nullptr);
  }
  if (null_image_3d_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImage(device, null_image_3d_, nullptr);
  }
  if (null_image_2d_array_cube_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImage(device, null_image_2d_array_cube_, nullptr);
  }
  for (VkDeviceMemory null_images_memory : null_images_memory_) {
    if (null_images_memory != VK_NULL_HANDLE) {
      dfn.vkFreeMemory(device, null_images_memory, nullptr);
    }
  }
  for (VkPipeline load_pipeline : load_pipelines_scaled_) {
    if (load_pipeline != VK_NULL_HANDLE) {
      dfn.vkDestroyPipeline(device, load_pipeline, nullptr);
    }
  }
  for (VkPipeline load_pipeline : load_pipelines_) {
    if (load_pipeline != VK_NULL_HANDLE) {
      dfn.vkDestroyPipeline(device, load_pipeline, nullptr);
    }
  }
  if (load_pipeline_layout_ != VK_NULL_HANDLE) {
    dfn.vkDestroyPipelineLayout(device, load_pipeline_layout_, nullptr);
  }

  // Textures memory is allocated using the Vulkan Memory Allocator, destroy all
  // textures before destroying VMA.
  DestroyAllTextures(true);

  // Clean up sparse scaled resolve resources (heaps and sparse buffers)
  ShutdownSparseScaledResolve();

  // Clean up simple scaled resolve buffers before destroying VMA
  // The command processor should ensure all GPU operations are complete
  // before the texture cache is destroyed
  for (ScaledResolveBuffer& buffer : scaled_resolve_buffers_) {
    if (buffer.buffer != VK_NULL_HANDLE) {
      vmaDestroyBuffer(vma_allocator_, buffer.buffer, buffer.allocation);
    }
  }
  scaled_resolve_buffers_.clear();

  if (vma_allocator_ != VK_NULL_HANDLE) {
    vmaDestroyAllocator(vma_allocator_);
  }
}

void VulkanTextureCache::BeginSubmission(uint64_t new_submission_index) {
  TextureCache::BeginSubmission(new_submission_index);

  if (!null_images_cleared_) {
    VkImage null_images[] = {null_image_2d_array_cube_, null_image_3d_};
    VkImageSubresourceRange null_image_subresource_range(
        ui::vulkan::util::InitializeSubresourceRange());
    for (size_t i = 0; i < xe::countof(null_images); ++i) {
      command_processor_.PushImageMemoryBarrier(
          null_images[i], null_image_subresource_range, 0,
          VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT,
          VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
          VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, false);
    }
    command_processor_.SubmitBarriers(true);
    DeferredCommandBuffer& command_buffer =
        command_processor_.deferred_command_buffer();
    // TODO(Triang3l): Find the return value for invalid texture fetch constants
    // on the real hardware.
    VkClearColorValue null_image_clear_color;
    null_image_clear_color.float32[0] = 0.0f;
    null_image_clear_color.float32[1] = 0.0f;
    null_image_clear_color.float32[2] = 0.0f;
    null_image_clear_color.float32[3] = 0.0f;
    for (size_t i = 0; i < xe::countof(null_images); ++i) {
      command_buffer.CmdVkClearColorImage(
          null_images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
          &null_image_clear_color, 1, &null_image_subresource_range);
    }
    for (size_t i = 0; i < xe::countof(null_images); ++i) {
      command_processor_.PushImageMemoryBarrier(
          null_images[i], null_image_subresource_range,
          VK_PIPELINE_STAGE_TRANSFER_BIT, guest_shader_pipeline_stages_,
          VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
          VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
          VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_QUEUE_FAMILY_IGNORED,
          VK_QUEUE_FAMILY_IGNORED, false);
    }
    null_images_cleared_ = true;
  }
}

void VulkanTextureCache::RequestTextures(uint32_t used_texture_mask) {
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
  SCOPE_profile_cpu_f("gpu");
#endif  // XE_GPU_FINE_GRAINED_DRAW_SCOPES

  TextureCache::RequestTextures(used_texture_mask);

  // Transition the textures into the needed usage.
  VkPipelineStageFlags dst_stage_mask;
  VkAccessFlags dst_access_mask;
  VkImageLayout new_layout;
  GetTextureUsageMasks(VulkanTexture::Usage::kGuestShaderSampled,
                       dst_stage_mask, dst_access_mask, new_layout);
  uint32_t textures_remaining = used_texture_mask;
  uint32_t index;
  while (xe::bit_scan_forward(textures_remaining, &index)) {
    textures_remaining &= ~(uint32_t(1) << index);
    const TextureBinding* binding = GetValidTextureBinding(index);
    if (!binding) {
      continue;
    }
    VulkanTexture* binding_texture =
        static_cast<VulkanTexture*>(binding->texture);
    if (binding_texture != nullptr) {
      // Will be referenced by the command buffer, so mark as used.
      binding_texture->MarkAsUsed();
      VulkanTexture::Usage old_usage =
          binding_texture->SetUsage(VulkanTexture::Usage::kGuestShaderSampled);
      if (old_usage != VulkanTexture::Usage::kGuestShaderSampled) {
        VkPipelineStageFlags src_stage_mask;
        VkAccessFlags src_access_mask;
        VkImageLayout old_layout;
        GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
                             old_layout);
        command_processor_.PushImageMemoryBarrier(
            binding_texture->image(),
            ui::vulkan::util::InitializeSubresourceRange(), src_stage_mask,
            dst_stage_mask, src_access_mask, dst_access_mask, old_layout,
            new_layout);
      }
    }
    VulkanTexture* binding_texture_signed =
        static_cast<VulkanTexture*>(binding->texture_signed);
    if (binding_texture_signed != nullptr) {
      binding_texture_signed->MarkAsUsed();
      VulkanTexture::Usage old_usage = binding_texture_signed->SetUsage(
          VulkanTexture::Usage::kGuestShaderSampled);
      if (old_usage != VulkanTexture::Usage::kGuestShaderSampled) {
        VkPipelineStageFlags src_stage_mask;
        VkAccessFlags src_access_mask;
        VkImageLayout old_layout;
        GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
                             old_layout);
        command_processor_.PushImageMemoryBarrier(
            binding_texture_signed->image(),
            ui::vulkan::util::InitializeSubresourceRange(), src_stage_mask,
            dst_stage_mask, src_access_mask, dst_access_mask, old_layout,
            new_layout);
      }
    }
  }
}

VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(
    uint32_t fetch_constant_index, xenos::FetchOpDimension dimension,
    bool is_signed) {
  VkImageView image_view = VK_NULL_HANDLE;
  const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index);
  if (binding && AreDimensionsCompatible(dimension, binding->key.dimension)) {
    // Check for 3D texture sampled as 2D.
    bool force_special_view =
        (dimension == xenos::FetchOpDimension::k2D &&
         binding->key.dimension == xenos::DataDimension::k3D);

    if (force_special_view) {
      // Get the appropriate texture for signed/unsigned.
      // Respect swizzled_signs from fetch constant, not just shader request.
      Texture* texture = nullptr;
      bool use_signed =
          is_signed && texture_util::IsAnySignSigned(binding->swizzled_signs);
      if (use_signed && IsSignedVersionSeparateForFormat(binding->key)) {
        texture = binding->texture_signed;
      } else {
        texture = binding->texture;
      }
      if (texture) {
        image_view =
            static_cast<VulkanTexture*>(texture)->GetOrCreate3DAs2DImageView(
                use_signed, binding->host_swizzle);
      }
    } else {
      const VulkanTextureBinding& vulkan_binding =
          vulkan_texture_bindings_[fetch_constant_index];
      image_view = is_signed ? vulkan_binding.image_view_signed
                             : vulkan_binding.image_view_unsigned;
    }
  }
  if (image_view != VK_NULL_HANDLE) {
    return image_view;
  }
  switch (dimension) {
    case xenos::FetchOpDimension::k3DOrStacked:
      return null_image_view_3d_;
    case xenos::FetchOpDimension::kCube:
      return null_image_view_cube_;
    default:
      return null_image_view_2d_array_;
  }
}

VulkanTextureCache::SamplerParameters VulkanTextureCache::GetSamplerParameters(
    const VulkanShader::SamplerBinding& binding) const {
  const auto& regs = register_file();
  xenos::xe_gpu_texture_fetch_t fetch =
      regs.GetTextureFetch(binding.fetch_constant);

  SamplerParameters parameters;

  xenos::ClampMode fetch_clamp_x, fetch_clamp_y, fetch_clamp_z;
  texture_util::GetClampModesForDimension(fetch, fetch_clamp_x, fetch_clamp_y,
                                          fetch_clamp_z);
  parameters.clamp_x = NormalizeClampMode(fetch_clamp_x);
  parameters.clamp_y = NormalizeClampMode(fetch_clamp_y);
  parameters.clamp_z = NormalizeClampMode(fetch_clamp_z);
  if (xenos::ClampModeUsesBorder(parameters.clamp_x) ||
      xenos::ClampModeUsesBorder(parameters.clamp_y) ||
      xenos::ClampModeUsesBorder(parameters.clamp_z)) {
    parameters.border_color = fetch.border_color;
  } else {
    parameters.border_color = xenos::BorderColor::k_ABGR_Black;
  }

  xenos::TextureFilter mag_filter =
      binding.mag_filter == xenos::TextureFilter::kUseFetchConst
          ? fetch.mag_filter
          : binding.mag_filter;
  parameters.mag_linear = mag_filter == xenos::TextureFilter::kLinear;
  xenos::TextureFilter min_filter =
      binding.min_filter == xenos::TextureFilter::kUseFetchConst
          ? fetch.min_filter
          : binding.min_filter;
  parameters.min_linear = min_filter == xenos::TextureFilter::kLinear;
  xenos::TextureFilter mip_filter =
      binding.mip_filter == xenos::TextureFilter::kUseFetchConst
          ? fetch.mip_filter
          : binding.mip_filter;
  parameters.mip_linear = mip_filter == xenos::TextureFilter::kLinear;
  if (parameters.mag_linear || parameters.min_linear || parameters.mip_linear) {
    // Check if the texture is actually filterable on the host.
    bool linear_filterable = true;
    TextureKey texture_key;
    uint8_t texture_swizzled_signs;
    BindingInfoFromFetchConstant(fetch, texture_key, &texture_swizzled_signs);
    if (texture_key.is_valid) {
      const HostFormatPair& host_format_pair = GetHostFormatPair(texture_key);
      if ((texture_util::IsAnySignNotSigned(texture_swizzled_signs) &&
           !host_format_pair.format_unsigned.linear_filterable) ||
          (texture_util::IsAnySignSigned(texture_swizzled_signs) &&
           !host_format_pair.format_signed.linear_filterable)) {
        linear_filterable = false;
      }
    } else {
      linear_filterable = false;
    }
    if (!linear_filterable) {
      parameters.mag_linear = 0;
      parameters.min_linear = 0;
      parameters.mip_linear = 0;
    }
  }
  xenos::AnisoFilter aniso_filter =
      binding.aniso_filter == xenos::AnisoFilter::kUseFetchConst
          ? fetch.aniso_filter
          : binding.aniso_filter;
  parameters.mip_base_map = mip_filter == xenos::TextureFilter::kBaseMap;

  uint32_t mip_min_level, mip_max_level;
  texture_util::GetSubresourcesFromFetchConstant(
      fetch, nullptr, nullptr, nullptr, nullptr, nullptr, &mip_min_level,
      &mip_max_level);
  parameters.mip_min_level = mip_min_level;
  bool has_mips = mip_max_level > mip_min_level;
  // Apply anisotropic override, but only for mipmapped textures
  // that are already using bilinear/trilinear filtering.
  if (cvars::anisotropic_override > -1 && cvars::anisotropic_override < 6 &&
      has_mips && !parameters.mip_base_map && parameters.mag_linear &&
      parameters.min_linear &&
      (mip_filter == xenos::TextureFilter::kPoint ||
       mip_filter == xenos::TextureFilter::kLinear)) {
    aniso_filter = xenos::AnisoFilter(cvars::anisotropic_override);
  }
  parameters.aniso_filter = std::min(aniso_filter, max_anisotropy_);

  return parameters;
}

VkSampler VulkanTextureCache::UseSampler(SamplerParameters parameters,
                                         bool& has_overflown_out) {
  assert_true(command_processor_.submission_open());
  uint64_t submission_current = command_processor_.GetCurrentSubmission();

  // Try to find an existing sampler.
  auto it_existing = samplers_.find(parameters);
  if (it_existing != samplers_.end()) {
    std::pair<const SamplerParameters, Sampler>& sampler = *it_existing;
    assert_true(sampler.second.last_usage_submission <= submission_current);
    // This is called very frequently, don't relink unless needed for caching.
    if (sampler.second.last_usage_submission < submission_current) {
      // Move to the front of the LRU queue.
      sampler.second.last_usage_submission = submission_current;
      if (sampler.second.used_next) {
        if (sampler.second.used_previous) {
          sampler.second.used_previous->second.used_next =
              sampler.second.used_next;
        } else {
          sampler_used_first_ = sampler.second.used_next;
        }
        sampler.second.used_next->second.used_previous =
            sampler.second.used_previous;
        sampler.second.used_previous = sampler_used_last_;
        sampler.second.used_next = nullptr;
        sampler_used_last_->second.used_next = &sampler;
        sampler_used_last_ = &sampler;
      }
    }
    has_overflown_out = false;
    return sampler.second.sampler;
  }

  const ui::vulkan::VulkanDevice* const vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();

  // See if an existing sampler can be destroyed to create space for the new
  // one.
  if (samplers_.size() >= sampler_max_count_) {
    assert_not_null(sampler_used_first_);
    if (!sampler_used_first_) {
      has_overflown_out = false;
      return VK_NULL_HANDLE;
    }
    if (sampler_used_first_->second.last_usage_submission >
        command_processor_.GetCompletedSubmission()) {
      has_overflown_out = true;
      return VK_NULL_HANDLE;
    }
    auto it_reuse = samplers_.find(sampler_used_first_->first);
    dfn.vkDestroySampler(device, sampler_used_first_->second.sampler, nullptr);
    if (sampler_used_first_->second.used_next) {
      sampler_used_first_->second.used_next->second.used_previous =
          sampler_used_first_->second.used_previous;
    } else {
      sampler_used_last_ = sampler_used_first_->second.used_previous;
    }
    sampler_used_first_ = sampler_used_first_->second.used_next;
    assert_true(it_reuse != samplers_.end());
    if (it_reuse != samplers_.end()) {
      // This destroys the Sampler object.
      samplers_.erase(it_reuse);
      COUNT_profile_set("gpu/texture_cache/vulkan/samplers", samplers_.size());
    } else {
      has_overflown_out = false;
      return VK_NULL_HANDLE;
    }
  }

  // Create a new sampler and make it the least recently used.
  // The values are normalized, and unsupported ones are excluded, in
  // GetSamplerParameters.
  VkSamplerCreateInfo sampler_create_info = {};
  sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
  if (vulkan_device->properties().nonSeamlessCubeMap &&
      cvars::non_seamless_cube_map) {
    sampler_create_info.flags |=
        VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT;
  }
  sampler_create_info.magFilter =
      parameters.mag_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
  sampler_create_info.minFilter =
      parameters.min_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
  sampler_create_info.mipmapMode = parameters.mip_linear
                                       ? VK_SAMPLER_MIPMAP_MODE_LINEAR
                                       : VK_SAMPLER_MIPMAP_MODE_NEAREST;
  static constexpr VkSamplerAddressMode kAddressModeMap[] = {
      // kRepeat
      VK_SAMPLER_ADDRESS_MODE_REPEAT,
      // kMirroredRepeat
      VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
      // kClampToEdge
      VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
      // kMirrorClampToEdge
      VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
      // kClampToHalfway
      VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
      // kMirrorClampToHalfway
      VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
      // kClampToBorder
      VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
      // kMirrorClampToBorder
      VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
  };
  sampler_create_info.addressModeU =
      kAddressModeMap[uint32_t(parameters.clamp_x)];
  sampler_create_info.addressModeV =
      kAddressModeMap[uint32_t(parameters.clamp_y)];
  sampler_create_info.addressModeW =
      kAddressModeMap[uint32_t(parameters.clamp_z)];
  // LOD biasing is performed in shaders.
  if (parameters.aniso_filter != xenos::AnisoFilter::kDisabled) {
    sampler_create_info.anisotropyEnable = VK_TRUE;
    sampler_create_info.maxAnisotropy =
        float(UINT32_C(1) << (uint32_t(parameters.aniso_filter) -
                              uint32_t(xenos::AnisoFilter::kMax_1_1)));
  }
  sampler_create_info.minLod = float(parameters.mip_min_level);
  if (parameters.mip_base_map) {
    assert_false(parameters.mip_linear);
    sampler_create_info.maxLod = sampler_create_info.minLod + 0.25f;
  } else {
    sampler_create_info.maxLod = VK_LOD_CLAMP_NONE;
  }
  // TODO(Triang3l): Custom border colors for CrYCb / YCrCb.
  switch (parameters.border_color) {
    case xenos::BorderColor::k_ABGR_White:
      sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
      break;
    default:
      sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
      break;
  }
  VkSampler vulkan_sampler;
  if (dfn.vkCreateSampler(device, &sampler_create_info, nullptr,
                          &vulkan_sampler) != VK_SUCCESS) {
    XELOGE(
        "VulkanTextureCache: Failed to create the sampler for parameters "
        "0x{:08X}",
        parameters.value);
    has_overflown_out = false;
    return VK_NULL_HANDLE;
  }
  std::pair<const SamplerParameters, Sampler>& new_sampler =
      *(samplers_
            .emplace(std::piecewise_construct,
                     std::forward_as_tuple(parameters), std::forward_as_tuple())
            .first);
  COUNT_profile_set("gpu/texture_cache/vulkan/samplers", samplers_.size());
  new_sampler.second.sampler = vulkan_sampler;
  new_sampler.second.last_usage_submission = submission_current;
  new_sampler.second.used_previous = sampler_used_last_;
  new_sampler.second.used_next = nullptr;
  if (sampler_used_last_) {
    sampler_used_last_->second.used_next = &new_sampler;
  } else {
    sampler_used_first_ = &new_sampler;
  }
  sampler_used_last_ = &new_sampler;
  return vulkan_sampler;
}

uint64_t VulkanTextureCache::GetSubmissionToAwaitOnSamplerOverflow(
    uint32_t overflowed_sampler_count) const {
  if (!overflowed_sampler_count) {
    return 0;
  }
  std::pair<const SamplerParameters, Sampler>* sampler_used =
      sampler_used_first_;
  if (!sampler_used_first_) {
    return 0;
  }
  for (uint32_t samplers_remaining = overflowed_sampler_count - 1;
       samplers_remaining; --samplers_remaining) {
    std::pair<const SamplerParameters, Sampler>* sampler_used_next =
        sampler_used->second.used_next;
    if (!sampler_used_next) {
      break;
    }
    sampler_used = sampler_used_next;
  }
  return sampler_used->second.last_usage_submission;
}

VkImageView VulkanTextureCache::RequestSwapTexture(
    uint32_t& width_scaled_out, uint32_t& height_scaled_out,
    xenos::TextureFormat& format_out) {
  const auto& regs = register_file();
  xenos::xe_gpu_texture_fetch_t fetch = regs.GetTextureFetch(0);
  TextureKey key;
  BindingInfoFromFetchConstant(fetch, key, nullptr);
  if (!key.is_valid || key.base_page == 0 ||
      key.dimension != xenos::DataDimension::k2DOrStacked) {
    return nullptr;
  }
  VulkanTexture* texture =
      static_cast<VulkanTexture*>(FindOrCreateTexture(key));
  if (!texture) {
    return VK_NULL_HANDLE;
  }
  VkImageView texture_view = texture->GetView(
      false, GuestToHostSwizzle(fetch.swizzle, GetHostFormatSwizzle(key)),
      false);
  if (texture_view == VK_NULL_HANDLE) {
    return VK_NULL_HANDLE;
  }
  if (!LoadTextureData(*texture)) {
    XELOGE("Failed to load texture data for swap texture");
    return VK_NULL_HANDLE;
  }
  texture->MarkAsUsed();
  VulkanTexture::Usage old_usage =
      texture->SetUsage(VulkanTexture::Usage::kSwapSampled);
  if (old_usage != VulkanTexture::Usage::kSwapSampled) {
    VkPipelineStageFlags src_stage_mask, dst_stage_mask;
    VkAccessFlags src_access_mask, dst_access_mask;
    VkImageLayout old_layout, new_layout;
    GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
                         old_layout);
    GetTextureUsageMasks(VulkanTexture::Usage::kSwapSampled, dst_stage_mask,
                         dst_access_mask, new_layout);
    command_processor_.PushImageMemoryBarrier(
        texture->image(), ui::vulkan::util::InitializeSubresourceRange(),
        src_stage_mask, dst_stage_mask, src_access_mask, dst_access_mask,
        old_layout, new_layout);
  }
  // Only texture->key, not the result of BindingInfoFromFetchConstant, contains
  // whether the texture is scaled.
  key = texture->key();
  width_scaled_out =
      key.GetWidth() * (key.scaled_resolve ? draw_resolution_scale_x() : 1);
  height_scaled_out =
      key.GetHeight() * (key.scaled_resolve ? draw_resolution_scale_y() : 1);
  format_out = key.format;
  return texture_view;
}

bool VulkanTextureCache::IsScaledResolveSupportedForFormat(
    TextureKey key) const {
  // Check if the format has a valid host format pair, meaning we can handle it
  const HostFormatPair& host_format_pair = GetHostFormatPair(key);
  return host_format_pair.format_unsigned.format != VK_FORMAT_UNDEFINED;
}

bool VulkanTextureCache::IsSignedVersionSeparateForFormat(
    TextureKey key) const {
  const HostFormatPair& host_format_pair = GetHostFormatPair(key);
  if (host_format_pair.format_unsigned.format == VK_FORMAT_UNDEFINED ||
      host_format_pair.format_signed.format == VK_FORMAT_UNDEFINED) {
    // Just one signedness.
    return false;
  }
  return !host_format_pair.unsigned_signed_compatible;
}

uint32_t VulkanTextureCache::GetHostFormatSwizzle(TextureKey key) const {
  return GetHostFormatPair(key).swizzle;
}

uint32_t VulkanTextureCache::GetMaxHostTextureWidthHeight(
    xenos::DataDimension dimension) const {
  const ui::vulkan::VulkanDevice::Properties& device_properties =
      command_processor_.GetVulkanDevice()->properties();
  switch (dimension) {
    case xenos::DataDimension::k1D:
    case xenos::DataDimension::k2DOrStacked:
      // 1D and 2D are emulated as 2D arrays.
      return device_properties.maxImageDimension2D;
    case xenos::DataDimension::k3D:
      return device_properties.maxImageDimension3D;
    case xenos::DataDimension::kCube:
      return device_properties.maxImageDimensionCube;
    default:
      assert_unhandled_case(dimension);
      return 0;
  }
}

uint32_t VulkanTextureCache::GetMaxHostTextureDepthOrArraySize(
    xenos::DataDimension dimension) const {
  const ui::vulkan::VulkanDevice::Properties& device_properties =
      command_processor_.GetVulkanDevice()->properties();
  switch (dimension) {
    case xenos::DataDimension::k1D:
    case xenos::DataDimension::k2DOrStacked:
      // 1D and 2D are emulated as 2D arrays.
      return device_properties.maxImageArrayLayers;
    case xenos::DataDimension::k3D:
      return device_properties.maxImageDimension3D;
    case xenos::DataDimension::kCube:
      // Not requesting the imageCubeArray feature, and the Xenos doesn't
      // support cube map arrays.
      return 6;
    default:
      assert_unhandled_case(dimension);
      return 0;
  }
}

std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
    TextureKey key) {
  VkFormat formats[] = {VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED};
  const HostFormatPair& host_format = GetHostFormatPair(key);
  if (host_format.format_signed.format == VK_FORMAT_UNDEFINED) {
    // Only the unsigned format may be available, if at all.
    formats[0] = host_format.format_unsigned.format;
  } else if (host_format.format_unsigned.format == VK_FORMAT_UNDEFINED) {
    // Only the signed format may be available, if at all.
    formats[0] = host_format.format_signed.format;
  } else {
    // Both unsigned and signed formats are available.
    if (IsSignedVersionSeparateForFormat(key)) {
      formats[0] = key.signed_separate ? host_format.format_signed.format
                                       : host_format.format_unsigned.format;
    } else {
      // Same format for unsigned and signed, or compatible formats.
      formats[0] = host_format.format_unsigned.format;
      if (host_format.format_signed.format !=
          host_format.format_unsigned.format) {
        assert_not_zero(host_format.unsigned_signed_compatible);
        formats[1] = host_format.format_signed.format;
      }
    }
  }
  if (formats[0] == VK_FORMAT_UNDEFINED) {
    // TODO(Triang3l): If there's no best format, set that a format unsupported
    // by the emulator completely is used to report at the end of the frame.
    return nullptr;
  }

  const ui::vulkan::VulkanDevice* const vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();

  bool is_3d = key.dimension == xenos::DataDimension::k3D;
  uint32_t depth_or_array_size = key.GetDepthOrArraySize();

  VkImageCreateInfo image_create_info;
  VkImageCreateInfo* image_create_info_last = &image_create_info;
  image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
  image_create_info.pNext = nullptr;
  image_create_info.flags = 0;
  if (formats[1] != VK_FORMAT_UNDEFINED) {
    image_create_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
  }
  if (key.dimension == xenos::DataDimension::kCube) {
    image_create_info.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
  }
  image_create_info.imageType = is_3d ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
  image_create_info.format = formats[0];
  image_create_info.extent.width = key.GetWidth();
  image_create_info.extent.height = key.GetHeight();
  if (key.scaled_resolve) {
    image_create_info.extent.width *= draw_resolution_scale_x();
    image_create_info.extent.height *= draw_resolution_scale_y();
  }
  image_create_info.extent.depth = is_3d ? depth_or_array_size : 1;
  image_create_info.mipLevels = key.mip_max_level + 1;
  image_create_info.arrayLayers = is_3d ? 1 : depth_or_array_size;
  image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
  image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
  image_create_info.usage =
      VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
  // For scaled resolve textures with mips, we need transfer source to generate
  // mip levels via blit from the base level.
  if (key.scaled_resolve && key.mip_max_level > 0) {
    image_create_info.usage |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
  }
  image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  image_create_info.queueFamilyIndexCount = 0;
  image_create_info.pQueueFamilyIndices = nullptr;
  image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  VkImageFormatListCreateInfo image_format_list_create_info;
  if (formats[1] != VK_FORMAT_UNDEFINED &&
      vulkan_device->extensions().ext_1_2_KHR_image_format_list) {
    image_create_info_last->pNext = &image_format_list_create_info;
    image_create_info_last =
        reinterpret_cast<VkImageCreateInfo*>(&image_format_list_create_info);
    image_format_list_create_info.sType =
        VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO;
    image_format_list_create_info.pNext = nullptr;
    image_format_list_create_info.viewFormatCount = 2;
    image_format_list_create_info.pViewFormats = formats;
  }

  VmaAllocationCreateInfo allocation_create_info = {};
  allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;

  VkImage image;
  VmaAllocation allocation;
  if (vmaCreateImage(vma_allocator_, &image_create_info,
                     &allocation_create_info, &image, &allocation, nullptr)) {
    return nullptr;
  }

  return std::unique_ptr<Texture>(
      new VulkanTexture(*this, key, image, allocation));
}

bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
                                                               bool load_base,
                                                               bool load_mips) {
  VulkanTexture& vulkan_texture = static_cast<VulkanTexture&>(texture);
  TextureKey texture_key = vulkan_texture.key();

  // Get the pipeline.
  const HostFormatPair& host_format_pair = GetHostFormatPair(texture_key);
  bool host_format_is_signed;
  if (IsSignedVersionSeparateForFormat(texture_key)) {
    host_format_is_signed = bool(texture_key.signed_separate);
  } else {
    host_format_is_signed =
        host_format_pair.format_unsigned.load_shader == kLoadShaderIndexUnknown;
  }
  const HostFormat& host_format = host_format_is_signed
                                      ? host_format_pair.format_signed
                                      : host_format_pair.format_unsigned;
  LoadShaderIndex load_shader = host_format.load_shader;
  if (load_shader == kLoadShaderIndexUnknown) {
    return false;
  }
  VkPipeline pipeline = texture_key.scaled_resolve
                            ? load_pipelines_scaled_[load_shader]
                            : load_pipelines_[load_shader];
  if (pipeline == VK_NULL_HANDLE) {
    return false;
  }
  const LoadShaderInfo& load_shader_info = GetLoadShaderInfo(load_shader);

  // Get the guest layout.
  const texture_util::TextureGuestLayout& guest_layout =
      vulkan_texture.guest_layout();
  xenos::DataDimension dimension = texture_key.dimension;
  // Whether the host image is 3D (determines depth vs array layer layout).
  bool is_3d = dimension == xenos::DataDimension::k3D;
  // Whether to use 3D tiling when reading from guest memory.
  // For 3D-as-2D wrappers, the host image is 2D but we need 3D tiling.
  bool is_3d_tiling = is_3d || vulkan_texture.force_load_3d_tiling();
  uint32_t width = texture_key.GetWidth();
  uint32_t height = texture_key.GetHeight();
  uint32_t depth_or_array_size = texture_key.GetDepthOrArraySize();
  uint32_t depth = is_3d ? depth_or_array_size : 1;
  uint32_t array_size = is_3d ? 1 : depth_or_array_size;
  xenos::TextureFormat guest_format = texture_key.format;
  const FormatInfo* guest_format_info = FormatInfo::Get(guest_format);
  uint32_t block_width = guest_format_info->block_width;
  uint32_t block_height = guest_format_info->block_height;
  uint32_t bytes_per_block = guest_format_info->bytes_per_block();
  uint32_t level_first = load_base ? 0 : 1;
  uint32_t level_last = load_mips ? texture_key.mip_max_level : 0;
  // For scaled resolve textures, we only load level 0 from the scaled buffer -
  // mips will be generated via blit.
  uint32_t level_last_for_blit_gen = 0;
  if (texture_key.scaled_resolve && level_last > 0) {
    level_last_for_blit_gen = level_last;
    level_last = 0;  // Only load base level from buffer
  }
  assert_true(level_first <= level_last || level_last_for_blit_gen > 0);
  uint32_t level_packed = guest_layout.packed_level;
  uint32_t level_stored_first = std::min(level_first, level_packed);
  uint32_t level_stored_last = std::min(level_last, level_packed);
  uint32_t texture_resolution_scale_x =
      texture_key.scaled_resolve ? draw_resolution_scale_x() : 1;
  uint32_t texture_resolution_scale_y =
      texture_key.scaled_resolve ? draw_resolution_scale_y() : 1;

  // The loop counter can mean two things depending on whether the packed mip
  // tail is stored as mip 0, because in this case, it would be ambiguous since
  // both the base and the mips would be on "level 0", but stored in separate
  // places.
  uint32_t loop_level_first, loop_level_last;
  if (level_packed == 0) {
    // Packed mip tail is the level 0 - may need to load mip tails for the base,
    // the mips, or both.
    // Loop iteration 0 - base packed mip tail.
    // Loop iteration 1 - mips packed mip tail.
    loop_level_first = uint32_t(level_first != 0);
    loop_level_last = uint32_t(level_last != 0);
  } else {
    // Packed mip tail is not the level 0.
    // Loop iteration is the actual level being loaded.
    loop_level_first = level_stored_first;
    loop_level_last = level_stored_last;
  }

  // Get the host layout and the buffer.
  uint32_t host_block_width = host_format.block_compressed ? block_width : 1;
  uint32_t host_block_height = host_format.block_compressed ? block_height : 1;
  uint32_t host_x_blocks_per_thread =
      UINT32_C(1) << load_shader_info.guest_x_blocks_per_thread_log2;
  if (!host_format.block_compressed) {
    // Decompressing guest blocks.
    host_x_blocks_per_thread *= block_width;
  }
  VkDeviceSize host_buffer_size = 0;
  struct HostLayout {
    VkDeviceSize offset_bytes;
    VkDeviceSize slice_size_bytes;
    uint32_t x_pitch_blocks;
    uint32_t y_pitch_blocks;
  };
  HostLayout host_layout_base;
  // Indexing is the same as for guest stored mips:
  // 1...min(level_last, level_packed) if level_packed is not 0, or only 0 if
  // level_packed == 0.
  HostLayout host_layout_mips[xenos::kTextureMaxMips];
  for (uint32_t loop_level = loop_level_first; loop_level <= loop_level_last;
       ++loop_level) {
    bool is_base = loop_level == 0;
    uint32_t level = (level_packed == 0) ? 0 : loop_level;
    HostLayout& level_host_layout =
        is_base ? host_layout_base : host_layout_mips[level];
    level_host_layout.offset_bytes = host_buffer_size;
    uint32_t level_guest_x_extent_texels_unscaled;
    uint32_t level_guest_y_extent_texels_unscaled;
    uint32_t level_guest_z_extent_texels;
    if (level == level_packed) {
      // Loading the packed tail for the base or the mips - load the whole tail
      // to copy regions out of it.
      const texture_util::TextureGuestLayout::Level& guest_layout_packed =
          is_base ? guest_layout.base : guest_layout.mips[level];
      level_guest_x_extent_texels_unscaled =
          guest_layout_packed.x_extent_blocks * block_width;
      level_guest_y_extent_texels_unscaled =
          guest_layout_packed.y_extent_blocks * block_height;
      level_guest_z_extent_texels = guest_layout_packed.z_extent;
    } else {
      level_guest_x_extent_texels_unscaled =
          std::max(width >> level, UINT32_C(1));
      level_guest_y_extent_texels_unscaled =
          std::max(height >> level, UINT32_C(1));
      level_guest_z_extent_texels = std::max(depth >> level, UINT32_C(1));
    }
    level_host_layout.x_pitch_blocks = xe::round_up(
        (level_guest_x_extent_texels_unscaled * texture_resolution_scale_x +
         (host_block_width - 1)) /
            host_block_width,
        host_x_blocks_per_thread);
    level_host_layout.y_pitch_blocks =
        (level_guest_y_extent_texels_unscaled * texture_resolution_scale_y +
         (host_block_height - 1)) /
        host_block_height;
    level_host_layout.slice_size_bytes =
        VkDeviceSize(load_shader_info.bytes_per_host_block) *
        level_host_layout.x_pitch_blocks * level_host_layout.y_pitch_blocks *
        level_guest_z_extent_texels;
    host_buffer_size += level_host_layout.slice_size_bytes * array_size;
  }
  VulkanCommandProcessor::ScratchBufferAcquisition scratch_buffer_acquisition(
      command_processor_.AcquireScratchGpuBuffer(
          host_buffer_size, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
          VK_ACCESS_SHADER_WRITE_BIT));
  VkBuffer scratch_buffer = scratch_buffer_acquisition.buffer();
  if (scratch_buffer == VK_NULL_HANDLE) {
    return false;
  }

  // Begin loading.
  // TODO(Triang3l): Going from one descriptor to another on per-array-layer
  // or even per-8-depth-slices level to stay within maxStorageBufferRange.
  const ui::vulkan::VulkanDevice* const vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();
  VulkanSharedMemory& vulkan_shared_memory =
      static_cast<VulkanSharedMemory&>(shared_memory());
  std::array<VkWriteDescriptorSet, 3> write_descriptor_sets;
  uint32_t write_descriptor_set_count = 0;
  VkDescriptorSet descriptor_set_dest =
      command_processor_.AllocateSingleTransientDescriptor(
          VulkanCommandProcessor::SingleTransientDescriptorLayout ::
              kStorageBufferCompute);
  if (!descriptor_set_dest) {
    return false;
  }
  VkDescriptorBufferInfo write_descriptor_set_dest_buffer_info;
  {
    write_descriptor_set_dest_buffer_info.buffer = scratch_buffer;
    write_descriptor_set_dest_buffer_info.offset = 0;
    write_descriptor_set_dest_buffer_info.range = host_buffer_size;
    VkWriteDescriptorSet& write_descriptor_set_dest =
        write_descriptor_sets[write_descriptor_set_count++];
    write_descriptor_set_dest.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write_descriptor_set_dest.pNext = nullptr;
    write_descriptor_set_dest.dstSet = descriptor_set_dest;
    write_descriptor_set_dest.dstBinding = 0;
    write_descriptor_set_dest.dstArrayElement = 0;
    write_descriptor_set_dest.descriptorCount = 1;
    write_descriptor_set_dest.descriptorType =
        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    write_descriptor_set_dest.pImageInfo = nullptr;
    write_descriptor_set_dest.pBufferInfo =
        &write_descriptor_set_dest_buffer_info;
    write_descriptor_set_dest.pTexelBufferView = nullptr;
  }
  // TODO(Triang3l): Use a single 512 MB shared memory binding if possible.
  VkDescriptorSet descriptor_set_source_base = VK_NULL_HANDLE;
  VkDescriptorSet descriptor_set_source_mips = VK_NULL_HANDLE;
  VkDescriptorBufferInfo write_descriptor_set_source_base_buffer_info;
  VkDescriptorBufferInfo write_descriptor_set_source_mips_buffer_info;
  if (level_first == 0) {
    descriptor_set_source_base =
        command_processor_.AllocateSingleTransientDescriptor(
            VulkanCommandProcessor::SingleTransientDescriptorLayout ::
                kStorageBufferCompute);
    if (!descriptor_set_source_base) {
      return false;
    }
    if (texture_key.scaled_resolve) {
      // For scaled textures, read from scaled resolve buffers
      uint32_t guest_address = texture_key.base_page << 12;
      uint32_t guest_size = vulkan_texture.GetGuestBaseSize();

      // Ensure the scaled buffer exists
      if (EnsureScaledResolveMemoryCommitted(guest_address, guest_size)) {
        // Make the range current
        if (MakeScaledResolveRangeCurrent(guest_address, guest_size)) {
          VkBuffer scaled_buffer = GetCurrentScaledResolveBuffer();
          if (scaled_buffer != VK_NULL_HANDLE) {
            // Calculate offset within the scaled buffer
            uint32_t draw_resolution_scale_area =
                draw_resolution_scale_x() * draw_resolution_scale_y();
            uint64_t scaled_offset =
                uint64_t(guest_address) * draw_resolution_scale_area;
            uint64_t buffer_relative_offset =
                scaled_offset - GetCurrentScaledResolveBufferBaseOffset();

            write_descriptor_set_source_base_buffer_info.buffer = scaled_buffer;
            write_descriptor_set_source_base_buffer_info.offset =
                buffer_relative_offset;
            // Align because shaders use up to 16-byte loads for multiple
            // blocks at once.
            write_descriptor_set_source_base_buffer_info.range = xe::align(
                guest_size * draw_resolution_scale_area, uint32_t(16));
          } else {
            XELOGE(
                "Scaled resolve texture load: Failed to get current scaled "
                "buffer for texture at 0x{:08X}",
                guest_address);
            return false;
          }
        } else {
          XELOGE(
              "Scaled resolve texture load: Failed to make range current for "
              "texture at 0x{:08X}",
              guest_address);
          return false;
        }
      } else {
        XELOGE(
            "Scaled resolve texture load: Failed to ensure scaled memory for "
            "texture at 0x{:08X}",
            guest_address);
        return false;
      }
    } else {
      // Regular unscaled texture - use shared memory
      write_descriptor_set_source_base_buffer_info.buffer =
          vulkan_shared_memory.buffer();
      write_descriptor_set_source_base_buffer_info.offset =
          texture_key.base_page << 12;
      // Align (primarily the last row of linear textures) because shaders use
      // up to 16-byte loads for multiple blocks at once.
      write_descriptor_set_source_base_buffer_info.range =
          xe::align(vulkan_texture.GetGuestBaseSize(), uint32_t(16));
    }
    VkWriteDescriptorSet& write_descriptor_set_source_base =
        write_descriptor_sets[write_descriptor_set_count++];
    write_descriptor_set_source_base.sType =
        VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write_descriptor_set_source_base.pNext = nullptr;
    write_descriptor_set_source_base.dstSet = descriptor_set_source_base;
    write_descriptor_set_source_base.dstBinding = 0;
    write_descriptor_set_source_base.dstArrayElement = 0;
    write_descriptor_set_source_base.descriptorCount = 1;
    write_descriptor_set_source_base.descriptorType =
        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    write_descriptor_set_source_base.pImageInfo = nullptr;
    write_descriptor_set_source_base.pBufferInfo =
        &write_descriptor_set_source_base_buffer_info;
    write_descriptor_set_source_base.pTexelBufferView = nullptr;
  }
  // For scaled resolve textures, we don't load mips from buffers - they will
  // be generated via blit from the base level. For unscaled textures, load
  // mips from shared memory as usual.
  if (level_last != 0 && !texture_key.scaled_resolve) {
    descriptor_set_source_mips =
        command_processor_.AllocateSingleTransientDescriptor(
            VulkanCommandProcessor::SingleTransientDescriptorLayout ::
                kStorageBufferCompute);
    if (!descriptor_set_source_mips) {
      return false;
    }
    // Regular unscaled texture - use shared memory
    write_descriptor_set_source_mips_buffer_info.buffer =
        vulkan_shared_memory.buffer();
    write_descriptor_set_source_mips_buffer_info.offset = texture_key.mip_page
                                                          << 12;
    // Align (primarily the last row of a linear packed mip tail) because
    // shaders use up to 16-byte loads for multiple blocks at once.
    write_descriptor_set_source_mips_buffer_info.range =
        xe::align(vulkan_texture.GetGuestMipsSize(), uint32_t(16));
    VkWriteDescriptorSet& write_descriptor_set_source_mips =
        write_descriptor_sets[write_descriptor_set_count++];
    write_descriptor_set_source_mips.sType =
        VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    write_descriptor_set_source_mips.pNext = nullptr;
    write_descriptor_set_source_mips.dstSet = descriptor_set_source_mips;
    write_descriptor_set_source_mips.dstBinding = 0;
    write_descriptor_set_source_mips.dstArrayElement = 0;
    write_descriptor_set_source_mips.descriptorCount = 1;
    write_descriptor_set_source_mips.descriptorType =
        VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
    write_descriptor_set_source_mips.pImageInfo = nullptr;
    write_descriptor_set_source_mips.pBufferInfo =
        &write_descriptor_set_source_mips_buffer_info;
    write_descriptor_set_source_mips.pTexelBufferView = nullptr;
  }
  if (write_descriptor_set_count) {
    dfn.vkUpdateDescriptorSets(device, write_descriptor_set_count,
                               write_descriptor_sets.data(), 0, nullptr);
  }
  vulkan_shared_memory.Use(VulkanSharedMemory::Usage::kRead);

  // Submit the copy buffer population commands.

  DeferredCommandBuffer& command_buffer =
      command_processor_.deferred_command_buffer();

  command_processor_.BindExternalComputePipeline(pipeline);

  command_buffer.CmdVkBindDescriptorSets(
      VK_PIPELINE_BIND_POINT_COMPUTE, load_pipeline_layout_,
      kLoadDescriptorSetIndexDestination, 1, &descriptor_set_dest, 0, nullptr);

  VkDescriptorSet descriptor_set_source_current = VK_NULL_HANDLE;

  LoadConstants load_constants;
  // 3 bits for each.
  assert_true(texture_resolution_scale_x <= 7);
  assert_true(texture_resolution_scale_y <= 7);
  load_constants.is_tiled_3d_endian_scale =
      uint32_t(texture_key.tiled) | (uint32_t(is_3d_tiling) << 1) |
      (uint32_t(texture_key.endianness) << 2) |
      (texture_resolution_scale_x << 4) | (texture_resolution_scale_y << 7);

  uint32_t guest_x_blocks_per_group_log2 =
      load_shader_info.GetGuestXBlocksPerGroupLog2();
  for (uint32_t loop_level = loop_level_first; loop_level <= loop_level_last;
       ++loop_level) {
    bool is_base = loop_level == 0;
    uint32_t level = (level_packed == 0) ? 0 : loop_level;

    VkDescriptorSet descriptor_set_source =
        is_base ? descriptor_set_source_base : descriptor_set_source_mips;
    if (descriptor_set_source_current != descriptor_set_source) {
      descriptor_set_source_current = descriptor_set_source;
      command_buffer.CmdVkBindDescriptorSets(
          VK_PIPELINE_BIND_POINT_COMPUTE, load_pipeline_layout_,
          kLoadDescriptorSetIndexSource, 1, &descriptor_set_source, 0, nullptr);
    }

    // TODO(Triang3l): guest_offset relative to the storage buffer origin.
    load_constants.guest_offset = 0;
    if (!is_base) {
      load_constants.guest_offset +=
          guest_layout.mip_offsets_bytes[level] *
          (texture_resolution_scale_x * texture_resolution_scale_y);
    }
    const texture_util::TextureGuestLayout::Level& level_guest_layout =
        is_base ? guest_layout.base : guest_layout.mips[level];
    load_constants.guest_pitch_aligned =
        level_guest_layout.row_pitch_bytes / bytes_per_block;
    load_constants.guest_z_stride_block_rows_aligned =
        level_guest_layout.z_slice_stride_block_rows;
    assert_true(dimension != xenos::DataDimension::k3D ||
                !(load_constants.guest_z_stride_block_rows_aligned &
                  (xenos::kTextureTileWidthHeight - 1)));

    uint32_t level_width, level_height, level_depth;
    if (level == level_packed) {
      // This is the packed mip tail, containing not only the specified level,
      // but also other levels at different offsets - load the entire needed
      // extents.
      level_width = level_guest_layout.x_extent_blocks * block_width;
      level_height = level_guest_layout.y_extent_blocks * block_height;
      level_depth = level_guest_layout.z_extent;
    } else {
      level_width = std::max(width >> level, UINT32_C(1));
      level_height = std::max(height >> level, UINT32_C(1));
      level_depth = std::max(depth >> level, UINT32_C(1));
    }
    load_constants.size_blocks[0] = (level_width + (block_width - 1)) /
                                    block_width * texture_resolution_scale_x;
    load_constants.size_blocks[1] = (level_height + (block_height - 1)) /
                                    block_height * texture_resolution_scale_y;
    load_constants.size_blocks[2] = level_depth;
    load_constants.height_texels = level_height;

    uint32_t group_count_x =
        (load_constants.size_blocks[0] +
         ((UINT32_C(1) << guest_x_blocks_per_group_log2) - 1)) >>
        guest_x_blocks_per_group_log2;
    uint32_t group_count_y =
        (load_constants.size_blocks[1] +
         ((UINT32_C(1) << kLoadGuestYBlocksPerGroupLog2) - 1)) >>
        kLoadGuestYBlocksPerGroupLog2;

    // TODO(Triang3l): host_offset relative to the storage buffer origin.
    const HostLayout& level_host_layout =
        is_base ? host_layout_base : host_layout_mips[level];
    load_constants.host_offset = uint32_t(level_host_layout.offset_bytes);
    load_constants.host_pitch = load_shader_info.bytes_per_host_block *
                                level_host_layout.x_pitch_blocks;

    command_buffer.CmdVkPushConstants(load_pipeline_layout_,
                                      VK_SHADER_STAGE_COMPUTE_BIT, 0,
                                      sizeof(load_constants), &load_constants);

    uint32_t level_array_slice_stride_bytes_scaled =
        level_guest_layout.array_slice_stride_bytes *
        (texture_resolution_scale_x * texture_resolution_scale_y);
    for (uint32_t slice = 0; slice < array_size; ++slice) {
      if (slice != 0) {
        command_buffer.CmdVkPushConstants(
            load_pipeline_layout_, VK_SHADER_STAGE_COMPUTE_BIT,
            offsetof(LoadConstants, guest_offset),
            sizeof(load_constants.guest_offset), &load_constants.guest_offset);
        command_buffer.CmdVkPushConstants(
            load_pipeline_layout_, VK_SHADER_STAGE_COMPUTE_BIT,
            offsetof(LoadConstants, host_offset),
            sizeof(load_constants.host_offset), &load_constants.host_offset);
      }
      command_processor_.SubmitBarriers(true);
      command_buffer.CmdVkDispatch(group_count_x, group_count_y,
                                   load_constants.size_blocks[2]);
      load_constants.guest_offset += level_array_slice_stride_bytes_scaled;
      load_constants.host_offset +=
          uint32_t(level_host_layout.slice_size_bytes);
    }
  }

  // Submit copying from the copy buffer to the host texture.
  command_processor_.PushBufferMemoryBarrier(
      scratch_buffer, 0, VK_WHOLE_SIZE,
      scratch_buffer_acquisition.SetStageMask(VK_PIPELINE_STAGE_TRANSFER_BIT),
      VK_PIPELINE_STAGE_TRANSFER_BIT,
      scratch_buffer_acquisition.SetAccessMask(VK_ACCESS_TRANSFER_READ_BIT),
      VK_ACCESS_TRANSFER_READ_BIT);
  vulkan_texture.MarkAsUsed();
  VulkanTexture::Usage texture_old_usage =
      vulkan_texture.SetUsage(VulkanTexture::Usage::kTransferDestination);
  if (texture_old_usage != VulkanTexture::Usage::kTransferDestination) {
    VkPipelineStageFlags texture_src_stage_mask, texture_dst_stage_mask;
    VkAccessFlags texture_src_access_mask, texture_dst_access_mask;
    VkImageLayout texture_old_layout, texture_new_layout;
    GetTextureUsageMasks(texture_old_usage, texture_src_stage_mask,
                         texture_src_access_mask, texture_old_layout);
    GetTextureUsageMasks(VulkanTexture::Usage::kTransferDestination,
                         texture_dst_stage_mask, texture_dst_access_mask,
                         texture_new_layout);
    command_processor_.PushImageMemoryBarrier(
        vulkan_texture.image(), ui::vulkan::util::InitializeSubresourceRange(),
        texture_src_stage_mask, texture_dst_stage_mask, texture_src_access_mask,
        texture_dst_access_mask, texture_old_layout, texture_new_layout);
  }
  command_processor_.SubmitBarriers(true);
  VkBufferImageCopy* copy_regions = command_buffer.CmdCopyBufferToImageEmplace(
      scratch_buffer, vulkan_texture.image(),
      VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, level_last - level_first + 1);
  for (uint32_t level = level_first; level <= level_last; ++level) {
    VkBufferImageCopy& copy_region = copy_regions[level - level_first];
    const HostLayout& level_host_layout =
        level != 0 ? host_layout_mips[std::min(level, level_packed)]
                   : host_layout_base;
    copy_region.bufferOffset = level_host_layout.offset_bytes;
    if (level >= level_packed) {
      uint32_t level_offset_blocks_x, level_offset_blocks_y, level_offset_z;
      texture_util::GetPackedMipOffset(width, height, depth, guest_format,
                                       level, level_offset_blocks_x,
                                       level_offset_blocks_y, level_offset_z);
      uint32_t level_offset_host_blocks_x =
          texture_resolution_scale_x * level_offset_blocks_x;
      uint32_t level_offset_host_blocks_y =
          texture_resolution_scale_y * level_offset_blocks_y;
      if (!host_format.block_compressed) {
        level_offset_host_blocks_x *= block_width;
        level_offset_host_blocks_y *= block_height;
      }
      copy_region.bufferOffset +=
          load_shader_info.bytes_per_host_block *
          (level_offset_host_blocks_x +
           level_host_layout.x_pitch_blocks *
               (level_offset_host_blocks_y + level_host_layout.y_pitch_blocks *
                                                 VkDeviceSize(level_offset_z)));
    }
    copy_region.bufferRowLength =
        level_host_layout.x_pitch_blocks * host_block_width;
    copy_region.bufferImageHeight =
        level_host_layout.y_pitch_blocks * host_block_height;
    copy_region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    copy_region.imageSubresource.mipLevel = level;
    copy_region.imageSubresource.baseArrayLayer = 0;
    copy_region.imageSubresource.layerCount = array_size;
    copy_region.imageOffset.x = 0;
    copy_region.imageOffset.y = 0;
    copy_region.imageOffset.z = 0;
    copy_region.imageExtent.width =
        std::max((width * texture_resolution_scale_x) >> level, UINT32_C(1));
    copy_region.imageExtent.height =
        std::max((height * texture_resolution_scale_y) >> level, UINT32_C(1));
    copy_region.imageExtent.depth = std::max(depth >> level, UINT32_C(1));
  }

  // Generate mip levels for scaled resolve textures via blit.
  if (level_last_for_blit_gen > 0) {
    VkImage image = vulkan_texture.image();
    uint32_t scaled_width = width * texture_resolution_scale_x;
    uint32_t scaled_height = height * texture_resolution_scale_y;

    // Generate each mip level by blitting from the previous level.
    for (uint32_t level = 1; level <= level_last_for_blit_gen; ++level) {
      uint32_t src_width = std::max(scaled_width >> (level - 1), UINT32_C(1));
      uint32_t src_height = std::max(scaled_height >> (level - 1), UINT32_C(1));
      uint32_t src_depth = std::max(depth >> (level - 1), UINT32_C(1));
      uint32_t dst_width = std::max(scaled_width >> level, UINT32_C(1));
      uint32_t dst_height = std::max(scaled_height >> level, UINT32_C(1));
      uint32_t dst_depth = std::max(depth >> level, UINT32_C(1));
      // VkImageBlit offsets are int32_t - ensure dimensions fit.
      assert_true(src_width <= INT32_MAX && src_height <= INT32_MAX &&
                  src_depth <= INT32_MAX);
      assert_true(dst_width <= INT32_MAX && dst_height <= INT32_MAX &&
                  dst_depth <= INT32_MAX);

      // Transition source mip (level - 1) to TRANSFER_SRC_OPTIMAL.
      {
        VkImageMemoryBarrier src_barrier = {};
        src_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
        src_barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
        src_barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
        src_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
        src_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
        src_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        src_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
        src_barrier.image = image;
        src_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        src_barrier.subresourceRange.baseMipLevel = level - 1;
        src_barrier.subresourceRange.levelCount = 1;
        src_barrier.subresourceRange.baseArrayLayer = 0;
        src_barrier.subresourceRange.layerCount = array_size;
        command_buffer.CmdVkPipelineBarrier(
            VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0,
            0, nullptr, 0, nullptr, 1, &src_barrier);
      }

      // Blit from level - 1 to level.
      VkImageBlit blit_region = {};
      blit_region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
      blit_region.srcSubresource.mipLevel = level - 1;
      blit_region.srcSubresource.baseArrayLayer = 0;
      blit_region.srcSubresource.layerCount = array_size;
      blit_region.srcOffsets[0] = {0, 0, 0};
      blit_region.srcOffsets[1] = {static_cast<int32_t>(src_width),
                                   static_cast<int32_t>(src_height),
                                   static_cast<int32_t>(src_depth)};
      blit_region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
      blit_region.dstSubresource.mipLevel = level;
      blit_region.dstSubresource.baseArrayLayer = 0;
      blit_region.dstSubresource.layerCount = array_size;
      blit_region.dstOffsets[0] = {0, 0, 0};
      blit_region.dstOffsets[1] = {static_cast<int32_t>(dst_width),
                                   static_cast<int32_t>(dst_height),
                                   static_cast<int32_t>(dst_depth)};

      command_buffer.CmdVkBlitImage(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
                                    image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
                                    1, &blit_region, VK_FILTER_LINEAR);
    }

    // Transition all mip levels to the final layout (TRANSFER_DST for now,
    // will be transitioned to shader read when used).
    // Level 0 to level_last_for_blit_gen - 1 are in TRANSFER_SRC_OPTIMAL.
    // Level level_last_for_blit_gen is in TRANSFER_DST_OPTIMAL (no change
    // needed).
    if (level_last_for_blit_gen > 0) {
      VkImageMemoryBarrier final_barrier = {};
      final_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
      final_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
      final_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
      final_barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
      final_barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
      final_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
      final_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
      final_barrier.image = image;
      final_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
      final_barrier.subresourceRange.baseMipLevel = 0;
      final_barrier.subresourceRange.levelCount = level_last_for_blit_gen;
      final_barrier.subresourceRange.baseArrayLayer = 0;
      final_barrier.subresourceRange.layerCount = array_size;
      command_buffer.CmdVkPipelineBarrier(
          VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0,
          nullptr, 0, nullptr, 1, &final_barrier);
    }
  }

  return true;
}

void VulkanTextureCache::UpdateTextureBindingsImpl(
    uint32_t fetch_constant_mask) {
  uint32_t bindings_remaining = fetch_constant_mask;
  uint32_t binding_index;
  while (xe::bit_scan_forward(bindings_remaining, &binding_index)) {
    bindings_remaining &= ~(UINT32_C(1) << binding_index);
    VulkanTextureBinding& vulkan_binding =
        vulkan_texture_bindings_[binding_index];
    vulkan_binding.Reset();
    const TextureBinding* binding = GetValidTextureBinding(binding_index);
    if (!binding) {
      continue;
    }
    if (IsSignedVersionSeparateForFormat(binding->key)) {
      if (binding->texture &&
          texture_util::IsAnySignNotSigned(binding->swizzled_signs)) {
        vulkan_binding.image_view_unsigned =
            static_cast<VulkanTexture*>(binding->texture)
                ->GetView(false, binding->host_swizzle);
      }
      if (binding->texture_signed &&
          texture_util::IsAnySignSigned(binding->swizzled_signs)) {
        vulkan_binding.image_view_signed =
            static_cast<VulkanTexture*>(binding->texture_signed)
                ->GetView(true, binding->host_swizzle);
      }
    } else {
      VulkanTexture* texture = static_cast<VulkanTexture*>(binding->texture);
      if (texture) {
        if (texture_util::IsAnySignNotSigned(binding->swizzled_signs)) {
          vulkan_binding.image_view_unsigned =
              texture->GetView(false, binding->host_swizzle);
        }
        if (texture_util::IsAnySignSigned(binding->swizzled_signs)) {
          vulkan_binding.image_view_signed =
              texture->GetView(true, binding->host_swizzle);
        }
      }
    }
  }
}

VulkanTextureCache::VulkanTexture::VulkanTexture(
    VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
    VmaAllocation allocation, bool track_usage)
    : Texture(texture_cache, key, track_usage),
      image_(image),
      allocation_(allocation) {
  VmaAllocationInfo allocation_info;
  vmaGetAllocationInfo(texture_cache.vma_allocator_, allocation_,
                       &allocation_info);
  SetHostMemoryUsage(uint64_t(allocation_info.size));
}

VulkanTextureCache::VulkanTexture::~VulkanTexture() {
  const VulkanTextureCache& vulkan_texture_cache =
      static_cast<const VulkanTextureCache&>(texture_cache());
  const ui::vulkan::VulkanDevice* const vulkan_device =
      vulkan_texture_cache.command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();
  for (const auto& view_pair : views_) {
    dfn.vkDestroyImageView(device, view_pair.second, nullptr);
  }
  // Clean up 3D-as-2D image views. The texture_3d_as_2d_ wrapper will clean
  // itself up via unique_ptr destructor.
  if (image_view_3d_as_2d_unsigned_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImageView(device, image_view_3d_as_2d_unsigned_, nullptr);
  }
  if (image_view_3d_as_2d_signed_ != VK_NULL_HANDLE) {
    dfn.vkDestroyImageView(device, image_view_3d_as_2d_signed_, nullptr);
  }
  // texture_3d_as_2d_ is a unique_ptr and will destroy its image/allocation.
  vmaDestroyImage(vulkan_texture_cache.vma_allocator_, image_, allocation_);
}

VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
                                                       uint32_t host_swizzle,
                                                       bool is_array) {
  xenos::DataDimension dimension = key().dimension;
  if (dimension == xenos::DataDimension::k3D ||
      dimension == xenos::DataDimension::kCube) {
    is_array = false;
  }

  const VulkanTextureCache& vulkan_texture_cache =
      static_cast<const VulkanTextureCache&>(texture_cache());

  ViewKey view_key;

  const HostFormatPair& host_format_pair =
      vulkan_texture_cache.GetHostFormatPair(key());
  VkFormat format = (is_signed ? host_format_pair.format_signed
                               : host_format_pair.format_unsigned)
                        .format;
  if (format == VK_FORMAT_UNDEFINED) {
    return VK_NULL_HANDLE;
  }
  // If not distinguishing between unsigned and signed formats for the same
  // image, don't create two views. As this happens within an image, no need to
  // care about whether unsigned and signed images are separate - if they are
  // (or if there are only unsigned or only signed images), this image will have
  // either all views unsigned or all views signed.
  view_key.is_signed_separate_view =
      is_signed && (host_format_pair.format_signed.format !=
                    host_format_pair.format_unsigned.format);

  const ui::vulkan::VulkanDevice* const vulkan_device =
      vulkan_texture_cache.command_processor_.GetVulkanDevice();

  if (!vulkan_device->properties().imageViewFormatSwizzle) {
    host_swizzle = xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA;
  }
  view_key.host_swizzle = host_swizzle;

  view_key.is_array = uint32_t(is_array);

  // Try to find an existing view.
  auto it = views_.find(view_key);
  if (it != views_.end()) {
    return it->second;
  }

  // Create a new view.
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();
  VkImageViewCreateInfo view_create_info;
  view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
  view_create_info.pNext = nullptr;
  view_create_info.flags = 0;
  view_create_info.image = image();
  view_create_info.format = format;
  view_create_info.components.r = GetComponentSwizzle(host_swizzle, 0);
  view_create_info.components.g = GetComponentSwizzle(host_swizzle, 1);
  view_create_info.components.b = GetComponentSwizzle(host_swizzle, 2);
  view_create_info.components.a = GetComponentSwizzle(host_swizzle, 3);
  view_create_info.subresourceRange =
      ui::vulkan::util::InitializeSubresourceRange();
  switch (dimension) {
    case xenos::DataDimension::k3D:
      view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
      break;
    case xenos::DataDimension::kCube:
      view_create_info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
      break;
    default:
      if (is_array) {
        view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
      } else {
        view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
        view_create_info.subresourceRange.layerCount = 1;
      }
      break;
  }
  VkImageView view;
  if (dfn.vkCreateImageView(device, &view_create_info, nullptr, &view) !=
      VK_SUCCESS) {
    XELOGE(
        "VulkanTextureCache: Failed to create an image view for Vulkan format "
        "{} ({}signed) with swizzle 0x{:3X}",
        uint32_t(format), is_signed ? "" : "un", host_swizzle);
    return VK_NULL_HANDLE;
  }
  views_.emplace(view_key, view);
  return view;
}

VkImageView VulkanTextureCache::VulkanTexture::GetOrCreate3DAs2DImageView(
    bool is_signed, uint32_t host_swizzle) {
  if (!cvars::gpu_3d_to_2d_texture) {
    return VK_NULL_HANDLE;
  }

  // Return cached view if available.
  VkImageView& cached_view =
      is_signed ? image_view_3d_as_2d_signed_ : image_view_3d_as_2d_unsigned_;
  if (cached_view != VK_NULL_HANDLE) {
    return cached_view;
  }

  VulkanTextureCache& vulkan_texture_cache =
      static_cast<VulkanTextureCache&>(texture_cache());

  // Create the 2D texture wrapper if it doesn't exist.
  if (!texture_3d_as_2d_) {
    const HostFormatPair& host_format_pair =
        vulkan_texture_cache.GetHostFormatPair(key());
    VkFormat format = host_format_pair.format_unsigned.format;
    if (format == VK_FORMAT_UNDEFINED) {
      return VK_NULL_HANDLE;
    }

    VkImageCreateInfo image_create_info = {};
    image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
    image_create_info.imageType = VK_IMAGE_TYPE_2D;
    image_create_info.format = format;
    image_create_info.extent.width = key().GetWidth();
    image_create_info.extent.height = key().GetHeight();
    image_create_info.extent.depth = 1;
    image_create_info.mipLevels = 1;
    image_create_info.arrayLayers = 1;
    image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
    image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
    image_create_info.usage =
        VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
    image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;

    VmaAllocationCreateInfo allocation_create_info = {};
    allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;

    VkImage image_2d;
    VmaAllocation allocation_2d;
    if (vmaCreateImage(vulkan_texture_cache.vma_allocator_, &image_create_info,
                       &allocation_create_info, &image_2d, &allocation_2d,
                       nullptr) != VK_SUCCESS) {
      XELOGE("VulkanTexture: Failed to create 3D-as-2D image");
      return VK_NULL_HANDLE;
    }

    // Create a modified key for the 2D wrapper with depth=1 and
    // mip_max_level=0. Keep dimension as k3D so guest layout uses 3D tiling
    // math to correctly read slice 0 from the 3D-tiled guest memory.
    TextureKey key_2d = key();
    key_2d.depth_or_array_size_minus_1 = 0;
    key_2d.mip_max_level = 0;

    // Create the wrapper first so LoadTextureData can work with it.
    texture_3d_as_2d_.reset(new VulkanTexture(vulkan_texture_cache, key_2d,
                                              image_2d, allocation_2d, false));

    if (!vulkan_texture_cache.LoadTextureData(*texture_3d_as_2d_)) {
      XELOGE("VulkanTexture: Failed to load 3D-as-2D texture data");
      texture_3d_as_2d_.reset();
      return VK_NULL_HANDLE;
    }

    // LoadTextureData leaves the texture in TRANSFER_DST state.
    // Transition to shader read state.
    DeferredCommandBuffer& command_buffer =
        vulkan_texture_cache.command_processor_.deferred_command_buffer();
    VkImageMemoryBarrier barrier_to_shader = {};
    barrier_to_shader.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
    barrier_to_shader.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
    barrier_to_shader.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
    barrier_to_shader.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
    barrier_to_shader.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
    barrier_to_shader.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier_to_shader.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
    barrier_to_shader.image = texture_3d_as_2d_->image();
    barrier_to_shader.subresourceRange =
        ui::vulkan::util::InitializeSubresourceRange();
    command_buffer.CmdVkPipelineBarrier(
        VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
        0, 0, nullptr, 0, nullptr, 1, &barrier_to_shader);
    texture_3d_as_2d_->SetUsage(Usage::kGuestShaderSampled);
  }

  // Create the image view.
  const ui::vulkan::VulkanDevice* const vulkan_device =
      vulkan_texture_cache.command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();

  const HostFormatPair& host_format_pair =
      vulkan_texture_cache.GetHostFormatPair(key());
  VkFormat format = (is_signed ? host_format_pair.format_signed
                               : host_format_pair.format_unsigned)
                        .format;
  if (format == VK_FORMAT_UNDEFINED) {
    return VK_NULL_HANDLE;
  }

  VkImageViewCreateInfo view_create_info = {};
  view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
  view_create_info.image = texture_3d_as_2d_->image();
  // Use 2D_ARRAY to match shader expectations (Dim = 2D, Arrayed = 1).
  view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
  view_create_info.format = format;
  view_create_info.components.r = GetComponentSwizzle(host_swizzle, 0);
  view_create_info.components.g = GetComponentSwizzle(host_swizzle, 1);
  view_create_info.components.b = GetComponentSwizzle(host_swizzle, 2);
  view_create_info.components.a = GetComponentSwizzle(host_swizzle, 3);
  view_create_info.subresourceRange =
      ui::vulkan::util::InitializeSubresourceRange();
  view_create_info.subresourceRange.layerCount = 1;

  if (dfn.vkCreateImageView(device, &view_create_info, nullptr, &cached_view) !=
      VK_SUCCESS) {
    XELOGE("VulkanTexture: Failed to create 3D-as-2D image view");
    return VK_NULL_HANDLE;
  }

  return cached_view;
}

VulkanTextureCache::VulkanTextureCache(
    const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
    uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
    VulkanCommandProcessor& command_processor,
    VkPipelineStageFlags guest_shader_pipeline_stages)
    : TextureCache(register_file, shared_memory, draw_resolution_scale_x,
                   draw_resolution_scale_y),
      command_processor_(command_processor),
      guest_shader_pipeline_stages_(guest_shader_pipeline_stages) {}

bool VulkanTextureCache::Initialize() {
  const ui::vulkan::VulkanDevice* const vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanInstance::Functions& ifn =
      vulkan_device->vulkan_instance()->functions();
  const VkPhysicalDevice physical_device = vulkan_device->physical_device();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  const VkDevice device = vulkan_device->device();
  const ui::vulkan::VulkanDevice::Properties& device_properties =
      vulkan_device->properties();

  // Vulkan Memory Allocator.

  vma_allocator_ = ui::vulkan::CreateVmaAllocator(vulkan_device, true);
  if (vma_allocator_ == VK_NULL_HANDLE) {
    return false;
  }

  // Image formats.

  // Initialize to the best formats.
  for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
    host_formats_[i] = kBestHostFormats[i];
  }

  // Check format support and switch to fallbacks if needed.
  constexpr VkFormatFeatureFlags kLinearFilterFeatures =
      VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
      VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
  VkFormatProperties r16_unorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(physical_device, VK_FORMAT_R16_UNORM,
                                          &r16_unorm_properties);
  VkFormatProperties r16_snorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(physical_device, VK_FORMAT_R16_SNORM,
                                          &r16_snorm_properties);
  VkFormatProperties r16g16_unorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_R16G16_UNORM, &r16g16_unorm_properties);
  VkFormatProperties r16g16_snorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_R16G16_SNORM, &r16g16_snorm_properties);
  VkFormatProperties r16g16b16a16_unorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
                                          VK_FORMAT_R16G16B16A16_UNORM,
                                          &r16g16b16a16_unorm_properties);
  VkFormatProperties r16g16b16a16_snorm_properties;
  ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
                                          VK_FORMAT_R16G16B16A16_SNORM,
                                          &r16g16b16a16_snorm_properties);
  VkFormatProperties format_properties;
  // TODO(Triang3l): k_2_10_10_10 signed -> filterable R16G16B16A16_SFLOAT
  // (enough storage precision, possibly unwanted filtering precision change).
  // k_Cr_Y1_Cb_Y0_REP, k_Y1_Cr_Y0_Cb_REP.
  HostFormatPair& host_format_gbgr =
      host_formats_[uint32_t(xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP)];
  assert_true(host_format_gbgr.format_unsigned.format ==
              VK_FORMAT_G8B8G8R8_422_UNORM_KHR);
  assert_true(host_format_gbgr.format_signed.format ==
              VK_FORMAT_R8G8B8A8_SNORM);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_G8B8G8R8_422_UNORM_KHR, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_gbgr.format_unsigned.load_shader = kLoadShaderIndexGBGR8ToRGB8;
    host_format_gbgr.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
    host_format_gbgr.format_unsigned.block_compressed = false;
    host_format_gbgr.unsigned_signed_compatible = true;
  }
  HostFormatPair& host_format_bgrg =
      host_formats_[uint32_t(xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP)];
  assert_true(host_format_bgrg.format_unsigned.format ==
              VK_FORMAT_B8G8R8G8_422_UNORM_KHR);
  assert_true(host_format_bgrg.format_signed.format ==
              VK_FORMAT_R8G8B8A8_SNORM);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_B8G8R8G8_422_UNORM_KHR, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_bgrg.format_unsigned.load_shader = kLoadShaderIndexBGRG8ToRGB8;
    host_format_bgrg.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
    host_format_bgrg.format_unsigned.block_compressed = false;
    host_format_bgrg.unsigned_signed_compatible = true;
  }
  // TODO(Triang3l): k_10_11_11 -> filterable R16G16B16A16_SFLOAT (enough
  // storage precision, possibly unwanted filtering precision change).
  // TODO(Triang3l): k_11_11_10 -> filterable R16G16B16A16_SFLOAT (enough
  // storage precision, possibly unwanted filtering precision change).
  // S3TC.
  // Not checking the textureCompressionBC feature because its availability
  // means that all BC formats are supported, however, the device may expose
  // some BC formats without this feature. Xenia doesn't use BC6H and BC7 at
  // all, and has fallbacks for each used format.
  // TODO(Triang3l): Raise the host texture memory usage limit if S3TC has to be
  // decompressed.
  // TODO(Triang3l): S3TC -> 5551 or 4444 as an option.
  // TODO(Triang3l): S3TC -> ETC2 / EAC (a huge research topic).
  HostFormatPair& host_format_dxt1 =
      host_formats_[uint32_t(xenos::TextureFormat::k_DXT1)];
  assert_true(host_format_dxt1.format_unsigned.format ==
              VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_dxt1.format_unsigned.load_shader = kLoadShaderIndexDXT1ToRGBA8;
    host_format_dxt1.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
    host_format_dxt1.format_unsigned.block_compressed = false;
    host_formats_[uint32_t(xenos::TextureFormat::k_DXT1_AS_16_16_16_16)] =
        host_format_dxt1;
  }
  HostFormatPair& host_format_dxt2_3 =
      host_formats_[uint32_t(xenos::TextureFormat::k_DXT2_3)];
  assert_true(host_format_dxt2_3.format_unsigned.format ==
              VK_FORMAT_BC2_UNORM_BLOCK);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_BC2_UNORM_BLOCK, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_dxt2_3.format_unsigned.load_shader =
        kLoadShaderIndexDXT3ToRGBA8;
    host_format_dxt2_3.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
    host_format_dxt2_3.format_unsigned.block_compressed = false;
    host_formats_[uint32_t(xenos::TextureFormat::k_DXT2_3_AS_16_16_16_16)] =
        host_format_dxt2_3;
  }
  HostFormatPair& host_format_dxt4_5 =
      host_formats_[uint32_t(xenos::TextureFormat::k_DXT4_5)];
  assert_true(host_format_dxt4_5.format_unsigned.format ==
              VK_FORMAT_BC3_UNORM_BLOCK);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_BC3_UNORM_BLOCK, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_dxt4_5.format_unsigned.load_shader =
        kLoadShaderIndexDXT5ToRGBA8;
    host_format_dxt4_5.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
    host_format_dxt4_5.format_unsigned.block_compressed = false;
    host_formats_[uint32_t(xenos::TextureFormat::k_DXT4_5_AS_16_16_16_16)] =
        host_format_dxt4_5;
  }
  HostFormatPair& host_format_dxn =
      host_formats_[uint32_t(xenos::TextureFormat::k_DXN)];
  assert_true(host_format_dxn.format_unsigned.format ==
              VK_FORMAT_BC5_UNORM_BLOCK);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_BC5_UNORM_BLOCK, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_dxn.format_unsigned.load_shader = kLoadShaderIndexDXNToRG8;
    host_format_dxn.format_unsigned.format = VK_FORMAT_R8G8_UNORM;
    host_format_dxn.format_unsigned.block_compressed = false;
  }
  HostFormatPair& host_format_dxt5a =
      host_formats_[uint32_t(xenos::TextureFormat::k_DXT5A)];
  assert_true(host_format_dxt5a.format_unsigned.format ==
              VK_FORMAT_BC4_UNORM_BLOCK);
  ifn.vkGetPhysicalDeviceFormatProperties(
      physical_device, VK_FORMAT_BC4_UNORM_BLOCK, &format_properties);
  if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
      kLinearFilterFeatures) {
    host_format_dxt5a.format_unsigned.load_shader = kLoadShaderIndexDXT5AToR8;
    host_format_dxt5a.format_unsigned.format = VK_FORMAT_R8_UNORM;
    host_format_dxt5a.format_unsigned.block_compressed = false;
  }
  // k_16, k_16_16, k_16_16_16_16 - UNORM / SNORM are optional, fall back to
  // SFLOAT, which is mandatory and is always filterable (the guest 16-bit
  // format is filterable, 16-bit fixed-point is the full texture filtering
  // precision on the Xenos overall). Let the user choose what's more important,
  // precision (use host UNORM / SNORM if available even if they're not
  // filterable) or filterability (use host UNORM / SNORM only if they're
  // available and filterable).
  // TODO(Triang3l): Expose a cvar for selecting the preference (filterability
  // or precision).
  VkFormatFeatureFlags norm16_required_features =
      VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
  HostFormatPair& host_format_16 =
      host_formats_[uint32_t(xenos::TextureFormat::k_16)];
  assert_true(host_format_16.format_unsigned.format == VK_FORMAT_R16_UNORM);
  if ((r16_unorm_properties.optimalTilingFeatures & norm16_required_features) !=
      norm16_required_features) {
    host_format_16.format_unsigned.load_shader =
        kLoadShaderIndexR16UNormToFloat;
    host_format_16.format_unsigned.format = VK_FORMAT_R16_SFLOAT;
  }
  assert_true(host_format_16.format_signed.format == VK_FORMAT_R16_SNORM);
  if ((r16_snorm_properties.optimalTilingFeatures & norm16_required_features) !=
      norm16_required_features) {
    host_format_16.format_signed.load_shader = kLoadShaderIndexR16SNormToFloat;
    host_format_16.format_signed.format = VK_FORMAT_R16_SFLOAT;
  }
  host_format_16.unsigned_signed_compatible =
      (host_format_16.format_unsigned.format == VK_FORMAT_R16_UNORM &&
       host_format_16.format_signed.format == VK_FORMAT_R16_SNORM) ||
      (host_format_16.format_unsigned.format == VK_FORMAT_R16_SFLOAT &&
       host_format_16.format_signed.format == VK_FORMAT_R16_SFLOAT);
  HostFormatPair& host_format_16_16 =
      host_formats_[uint32_t(xenos::TextureFormat::k_16_16)];
  assert_true(host_format_16_16.format_unsigned.format ==
              VK_FORMAT_R16G16_UNORM);
  if ((r16g16_unorm_properties.optimalTilingFeatures &
       norm16_required_features) != norm16_required_features) {
    host_format_16_16.format_unsigned.load_shader =
        kLoadShaderIndexRG16UNormToFloat;
    host_format_16_16.format_unsigned.format = VK_FORMAT_R16G16_SFLOAT;
  }
  assert_true(host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SNORM);
  if ((r16g16_snorm_properties.optimalTilingFeatures &
       norm16_required_features) != norm16_required_features) {
    host_format_16_16.format_signed.load_shader =
        kLoadShaderIndexRG16SNormToFloat;
    host_format_16_16.format_signed.format = VK_FORMAT_R16G16_SFLOAT;
  }
  host_format_16_16.unsigned_signed_compatible =
      (host_format_16_16.format_unsigned.format == VK_FORMAT_R16G16_UNORM &&
       host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SNORM) ||
      (host_format_16_16.format_unsigned.format == VK_FORMAT_R16G16_SFLOAT &&
       host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SFLOAT);
  HostFormatPair& host_format_16_16_16_16 =
      host_formats_[uint32_t(xenos::TextureFormat::k_16_16_16_16)];
  assert_true(host_format_16_16_16_16.format_unsigned.format ==
              VK_FORMAT_R16G16B16A16_UNORM);
  if ((r16g16b16a16_unorm_properties.optimalTilingFeatures &
       norm16_required_features) != norm16_required_features) {
    host_format_16_16_16_16.format_unsigned.load_shader =
        kLoadShaderIndexRGBA16UNormToFloat;
    host_format_16_16_16_16.format_unsigned.format =
        VK_FORMAT_R16G16B16A16_SFLOAT;
  }
  assert_true(host_format_16_16_16_16.format_signed.format ==
              VK_FORMAT_R16G16B16A16_SNORM);
  if ((r16g16b16a16_snorm_properties.optimalTilingFeatures &
       norm16_required_features) != norm16_required_features) {
    host_format_16_16_16_16.format_signed.load_shader =
        kLoadShaderIndexRGBA16SNormToFloat;
    host_format_16_16_16_16.format_signed.format =
        VK_FORMAT_R16G16B16A16_SFLOAT;
  }
  host_format_16_16_16_16.unsigned_signed_compatible =
      (host_format_16_16_16_16.format_unsigned.format ==
           VK_FORMAT_R16G16B16A16_UNORM &&
       host_format_16_16_16_16.format_signed.format ==
           VK_FORMAT_R16G16B16A16_SNORM) ||
      (host_format_16_16_16_16.format_unsigned.format ==
           VK_FORMAT_R16G16B16A16_SFLOAT &&
       host_format_16_16_16_16.format_signed.format ==
           VK_FORMAT_R16G16B16A16_SFLOAT);

  // Normalize format information structures.
  for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
    HostFormatPair& host_format = host_formats_[i];
    // load_shader_index is left uninitialized for the tail (non-existent
    // formats), kLoadShaderIndexUnknown may be non-zero, and format support may
    // be disabled by setting the format to VK_FORMAT_UNDEFINED.
    if (host_format.format_unsigned.format == VK_FORMAT_UNDEFINED) {
      host_format.format_unsigned.load_shader = kLoadShaderIndexUnknown;
    }
    assert_false(host_format.format_unsigned.load_shader ==
                     kLoadShaderIndexUnknown &&
                 host_format.format_unsigned.format != VK_FORMAT_UNDEFINED);
    if (host_format.format_unsigned.load_shader == kLoadShaderIndexUnknown) {
      host_format.format_unsigned.format = VK_FORMAT_UNDEFINED;
      // Surely known it's unsupported with these two conditions.
      host_format.format_unsigned.linear_filterable = false;
    }
    if (host_format.format_signed.format == VK_FORMAT_UNDEFINED) {
      host_format.format_signed.load_shader = kLoadShaderIndexUnknown;
    }
    assert_false(host_format.format_signed.load_shader ==
                     kLoadShaderIndexUnknown &&
                 host_format.format_signed.format != VK_FORMAT_UNDEFINED);
    if (host_format.format_signed.load_shader == kLoadShaderIndexUnknown) {
      host_format.format_signed.format = VK_FORMAT_UNDEFINED;
      // Surely known it's unsupported with these two conditions.
      host_format.format_signed.linear_filterable = false;
    }

    // Check if the formats are supported and are linear-filterable.
    if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
      ifn.vkGetPhysicalDeviceFormatProperties(
          physical_device, host_format.format_unsigned.format,
          &format_properties);
      if (format_properties.optimalTilingFeatures &
          VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
        host_format.format_unsigned.linear_filterable =
            (format_properties.optimalTilingFeatures &
             VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
      } else {
        host_format.format_unsigned.format = VK_FORMAT_UNDEFINED;
        host_format.format_unsigned.load_shader = kLoadShaderIndexUnknown;
        host_format.format_unsigned.linear_filterable = false;
      }
    }
    if (host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
      ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
                                              host_format.format_signed.format,
                                              &format_properties);
      if (format_properties.optimalTilingFeatures &
          VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
        host_format.format_signed.linear_filterable =
            (format_properties.optimalTilingFeatures &
             VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
      } else {
        host_format.format_signed.format = VK_FORMAT_UNDEFINED;
        host_format.format_signed.load_shader = kLoadShaderIndexUnknown;
        host_format.format_signed.linear_filterable = false;
      }
    }

    // Log which formats are not supported or supported via fallbacks.
    const HostFormatPair& best_host_format = kBestHostFormats[i];
    const char* guest_format_name =
        FormatInfo::GetName(xenos::TextureFormat(i));
    if (best_host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
      assert_not_null(guest_format_name);
      if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
        if (host_format.format_unsigned.format !=
            best_host_format.format_unsigned.format) {
          XELOGGPU(
              "VulkanTextureCache: Format {} (unsigned) is supported via a "
              "fallback format (using the Vulkan format {} instead of the "
              "preferred {})",
              guest_format_name, uint32_t(host_format.format_unsigned.format),
              uint32_t(best_host_format.format_unsigned.format));
        }
      } else {
        XELOGGPU(
            "VulkanTextureCache: Format {} (unsigned) is not supported by the "
            "device (preferred Vulkan format is {})",
            guest_format_name,
            uint32_t(best_host_format.format_unsigned.format));
      }
    }
    if (best_host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
      assert_not_null(guest_format_name);
      if (host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
        if (host_format.format_signed.format !=
            best_host_format.format_signed.format) {
          XELOGGPU(
              "VulkanTextureCache: Format {} (signed) is supported via a "
              "fallback format (using the Vulkan format {} instead of the "
              "preferred {})",
              guest_format_name, uint32_t(host_format.format_signed.format),
              uint32_t(best_host_format.format_signed.format));
        }
      } else {
        XELOGGPU(
            "VulkanTextureCache: Format {} (signed) is not supported by the "
            "device (preferred Vulkan format is {})",
            guest_format_name, uint32_t(best_host_format.format_signed.format));
      }
    }

    // Signednesses with different load shaders must have the data loaded
    // differently, therefore can't share the image even if the format is the
    // same. Also, if there's only one version, simplify the logic - there can't
    // be compatibility between two formats when one of them is undefined.
    if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED &&
        host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
      if (host_format.format_unsigned.load_shader ==
          host_format.format_signed.load_shader) {
        if (host_format.format_unsigned.format ==
            host_format.format_signed.format) {
          // Same format after all the fallbacks - force compatibilty.
          host_format.unsigned_signed_compatible = true;
        }
      } else {
        host_format.unsigned_signed_compatible = false;
      }
      // Formats within the same compatibility class must have the same block
      // size, though the fallbacks are configured incorrectly if that's not the
      // case (since such formats just can't be in one compatibility class).
      assert_false(host_format.unsigned_signed_compatible &&
                   host_format.format_unsigned.block_compressed !=
                       host_format.format_signed.block_compressed);
      if (host_format.unsigned_signed_compatible &&
          host_format.format_unsigned.block_compressed !=
              host_format.format_signed.block_compressed) {
        host_format.unsigned_signed_compatible = false;
      }
    } else {
      host_format.unsigned_signed_compatible = false;
    }
  }

  // Load pipeline layout.

  VkDescriptorSetLayout load_descriptor_set_layouts[kLoadDescriptorSetCount] =
      {};
  VkDescriptorSetLayout load_descriptor_set_layout_storage_buffer =
      command_processor_.GetSingleTransientDescriptorLayout(
          VulkanCommandProcessor::SingleTransientDescriptorLayout ::
              kStorageBufferCompute);
  assert_true(load_descriptor_set_layout_storage_buffer != VK_NULL_HANDLE);
  load_descriptor_set_layouts[kLoadDescriptorSetIndexDestination] =
      load_descriptor_set_layout_storage_buffer;
  load_descriptor_set_layouts[kLoadDescriptorSetIndexSource] =
      load_descriptor_set_layout_storage_buffer;
  VkPushConstantRange load_pipeline_layout_push_constant_range;
  load_pipeline_layout_push_constant_range.stageFlags =
      VK_SHADER_STAGE_COMPUTE_BIT;
  load_pipeline_layout_push_constant_range.offset = 0;
  load_pipeline_layout_push_constant_range.size = sizeof(LoadConstants);
  VkPipelineLayoutCreateInfo load_pipeline_layout_create_info;
  load_pipeline_layout_create_info.sType =
      VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
  load_pipeline_layout_create_info.pNext = nullptr;
  load_pipeline_layout_create_info.flags = 0;
  load_pipeline_layout_create_info.setLayoutCount = kLoadDescriptorSetCount;
  load_pipeline_layout_create_info.pSetLayouts = load_descriptor_set_layouts;
  load_pipeline_layout_create_info.pushConstantRangeCount = 1;
  load_pipeline_layout_create_info.pPushConstantRanges =
      &load_pipeline_layout_push_constant_range;
  if (dfn.vkCreatePipelineLayout(device, &load_pipeline_layout_create_info,
                                 nullptr, &load_pipeline_layout_)) {
    XELOGE("VulkanTexture: Failed to create the texture load pipeline layout");
    return false;
  }

  // Load pipelines, only the ones needed for the formats that will be used.

  bool load_shaders_needed[kLoadShaderCount] = {};
  for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
    const HostFormatPair& host_format = host_formats_[i];
    if (host_format.format_unsigned.load_shader != kLoadShaderIndexUnknown) {
      load_shaders_needed[host_format.format_unsigned.load_shader] = true;
    }
    if (host_format.format_signed.load_shader != kLoadShaderIndexUnknown) {
      load_shaders_needed[host_format.format_signed.load_shader] = true;
    }
  }
  if (kHostFormatGBGRUnaligned.format_unsigned.load_shader !=
      kLoadShaderIndexUnknown) {
    load_shaders_needed[kHostFormatGBGRUnaligned.format_unsigned.load_shader] =
        true;
  }
  if (kHostFormatGBGRUnaligned.format_signed.load_shader !=
      kLoadShaderIndexUnknown) {
    load_shaders_needed[kHostFormatGBGRUnaligned.format_signed.load_shader] =
        true;
  }
  if (kHostFormatBGRGUnaligned.format_unsigned.load_shader !=
      kLoadShaderIndexUnknown) {
    load_shaders_needed[kHostFormatBGRGUnaligned.format_unsigned.load_shader] =
        true;
  }
  if (kHostFormatBGRGUnaligned.format_signed.load_shader !=
      kLoadShaderIndexUnknown) {
    load_shaders_needed[kHostFormatBGRGUnaligned.format_signed.load_shader] =
        true;
  }

  std::pair<const uint32_t*, size_t> load_shader_code[kLoadShaderCount] = {};
  load_shader_code[kLoadShaderIndex8bpb] = std::make_pair(
      shaders::texture_load_8bpb_cs, sizeof(shaders::texture_load_8bpb_cs));
  load_shader_code[kLoadShaderIndex16bpb] = std::make_pair(
      shaders::texture_load_16bpb_cs, sizeof(shaders::texture_load_16bpb_cs));
  load_shader_code[kLoadShaderIndex32bpb] = std::make_pair(
      shaders::texture_load_32bpb_cs, sizeof(shaders::texture_load_32bpb_cs));
  load_shader_code[kLoadShaderIndex64bpb] = std::make_pair(
      shaders::texture_load_64bpb_cs, sizeof(shaders::texture_load_64bpb_cs));
  load_shader_code[kLoadShaderIndex128bpb] = std::make_pair(
      shaders::texture_load_128bpb_cs, sizeof(shaders::texture_load_128bpb_cs));
  load_shader_code[kLoadShaderIndexR5G5B5A1ToB5G5R5A1] =
      std::make_pair(shaders::texture_load_r5g5b5a1_b5g5r5a1_cs,
                     sizeof(shaders::texture_load_r5g5b5a1_b5g5r5a1_cs));
  load_shader_code[kLoadShaderIndexR5G6B5ToB5G6R5] =
      std::make_pair(shaders::texture_load_r5g6b5_b5g6r5_cs,
                     sizeof(shaders::texture_load_r5g6b5_b5g6r5_cs));
  load_shader_code[kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle] =
      std::make_pair(
          shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs,
          sizeof(shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs));
  load_shader_code[kLoadShaderIndexRGBA4ToARGB4] =
      std::make_pair(shaders::texture_load_r4g4b4a4_a4r4g4b4_cs,
                     sizeof(shaders::texture_load_r4g4b4a4_a4r4g4b4_cs));
  load_shader_code[kLoadShaderIndexGBGR8ToRGB8] =
      std::make_pair(shaders::texture_load_gbgr8_rgb8_cs,
                     sizeof(shaders::texture_load_gbgr8_rgb8_cs));
  load_shader_code[kLoadShaderIndexBGRG8ToRGB8] =
      std::make_pair(shaders::texture_load_bgrg8_rgb8_cs,
                     sizeof(shaders::texture_load_bgrg8_rgb8_cs));
  load_shader_code[kLoadShaderIndexR10G11B11ToRGBA16] =
      std::make_pair(shaders::texture_load_r10g11b11_rgba16_cs,
                     sizeof(shaders::texture_load_r10g11b11_rgba16_cs));
  load_shader_code[kLoadShaderIndexR10G11B11ToRGBA16SNorm] =
      std::make_pair(shaders::texture_load_r10g11b11_rgba16_snorm_cs,
                     sizeof(shaders::texture_load_r10g11b11_rgba16_snorm_cs));
  load_shader_code[kLoadShaderIndexR11G11B10ToRGBA16] =
      std::make_pair(shaders::texture_load_r11g11b10_rgba16_cs,
                     sizeof(shaders::texture_load_r11g11b10_rgba16_cs));
  load_shader_code[kLoadShaderIndexR11G11B10ToRGBA16SNorm] =
      std::make_pair(shaders::texture_load_r11g11b10_rgba16_snorm_cs,
                     sizeof(shaders::texture_load_r11g11b10_rgba16_snorm_cs));
  load_shader_code[kLoadShaderIndexR16UNormToFloat] =
      std::make_pair(shaders::texture_load_r16_unorm_float_cs,
                     sizeof(shaders::texture_load_r16_unorm_float_cs));
  load_shader_code[kLoadShaderIndexR16SNormToFloat] =
      std::make_pair(shaders::texture_load_r16_snorm_float_cs,
                     sizeof(shaders::texture_load_r16_snorm_float_cs));
  load_shader_code[kLoadShaderIndexRG16UNormToFloat] =
      std::make_pair(shaders::texture_load_rg16_unorm_float_cs,
                     sizeof(shaders::texture_load_rg16_unorm_float_cs));
  load_shader_code[kLoadShaderIndexRG16SNormToFloat] =
      std::make_pair(shaders::texture_load_rg16_snorm_float_cs,
                     sizeof(shaders::texture_load_rg16_snorm_float_cs));
  load_shader_code[kLoadShaderIndexRGBA16UNormToFloat] =
      std::make_pair(shaders::texture_load_rgba16_unorm_float_cs,
                     sizeof(shaders::texture_load_rgba16_unorm_float_cs));
  load_shader_code[kLoadShaderIndexRGBA16SNormToFloat] =
      std::make_pair(shaders::texture_load_rgba16_snorm_float_cs,
                     sizeof(shaders::texture_load_rgba16_snorm_float_cs));
  load_shader_code[kLoadShaderIndexDXT1ToRGBA8] =
      std::make_pair(shaders::texture_load_dxt1_rgba8_cs,
                     sizeof(shaders::texture_load_dxt1_rgba8_cs));
  load_shader_code[kLoadShaderIndexDXT3ToRGBA8] =
      std::make_pair(shaders::texture_load_dxt3_rgba8_cs,
                     sizeof(shaders::texture_load_dxt3_rgba8_cs));
  load_shader_code[kLoadShaderIndexDXT5ToRGBA8] =
      std::make_pair(shaders::texture_load_dxt5_rgba8_cs,
                     sizeof(shaders::texture_load_dxt5_rgba8_cs));
  load_shader_code[kLoadShaderIndexDXNToRG8] =
      std::make_pair(shaders::texture_load_dxn_rg8_cs,
                     sizeof(shaders::texture_load_dxn_rg8_cs));
  load_shader_code[kLoadShaderIndexDXT3A] = std::make_pair(
      shaders::texture_load_dxt3a_cs, sizeof(shaders::texture_load_dxt3a_cs));
  load_shader_code[kLoadShaderIndexDXT3AAs1111ToARGB4] =
      std::make_pair(shaders::texture_load_dxt3aas1111_argb4_cs,
                     sizeof(shaders::texture_load_dxt3aas1111_argb4_cs));
  load_shader_code[kLoadShaderIndexDXT5AToR8] =
      std::make_pair(shaders::texture_load_dxt5a_r8_cs,
                     sizeof(shaders::texture_load_dxt5a_r8_cs));
  load_shader_code[kLoadShaderIndexCTX1] = std::make_pair(
      shaders::texture_load_ctx1_cs, sizeof(shaders::texture_load_ctx1_cs));
  load_shader_code[kLoadShaderIndexDepthUnorm] =
      std::make_pair(shaders::texture_load_depth_unorm_cs,
                     sizeof(shaders::texture_load_depth_unorm_cs));
  load_shader_code[kLoadShaderIndexDepthFloat] =
      std::make_pair(shaders::texture_load_depth_float_cs,
                     sizeof(shaders::texture_load_depth_float_cs));
  std::pair<const uint32_t*, size_t> load_shader_code_scaled[kLoadShaderCount] =
      {};
  if (IsDrawResolutionScaled()) {
    load_shader_code_scaled[kLoadShaderIndex8bpb] =
        std::make_pair(shaders::texture_load_8bpb_scaled_cs,
                       sizeof(shaders::texture_load_8bpb_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndex16bpb] =
        std::make_pair(shaders::texture_load_16bpb_scaled_cs,
                       sizeof(shaders::texture_load_16bpb_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndex32bpb] =
        std::make_pair(shaders::texture_load_32bpb_scaled_cs,
                       sizeof(shaders::texture_load_32bpb_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndex64bpb] =
        std::make_pair(shaders::texture_load_64bpb_scaled_cs,
                       sizeof(shaders::texture_load_64bpb_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndex128bpb] =
        std::make_pair(shaders::texture_load_128bpb_scaled_cs,
                       sizeof(shaders::texture_load_128bpb_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR5G5B5A1ToB5G5R5A1] =
        std::make_pair(
            shaders::texture_load_r5g5b5a1_b5g5r5a1_scaled_cs,
            sizeof(shaders::texture_load_r5g5b5a1_b5g5r5a1_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR5G6B5ToB5G6R5] =
        std::make_pair(shaders::texture_load_r5g6b5_b5g6r5_scaled_cs,
                       sizeof(shaders::texture_load_r5g6b5_b5g6r5_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle] =
        std::make_pair(
            shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs,
            sizeof(shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexRGBA4ToARGB4] = std::make_pair(
        shaders::texture_load_r4g4b4a4_a4r4g4b4_scaled_cs,
        sizeof(shaders::texture_load_r4g4b4a4_a4r4g4b4_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR10G11B11ToRGBA16] = std::make_pair(
        shaders::texture_load_r10g11b11_rgba16_scaled_cs,
        sizeof(shaders::texture_load_r10g11b11_rgba16_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR10G11B11ToRGBA16SNorm] =
        std::make_pair(
            shaders::texture_load_r10g11b11_rgba16_snorm_scaled_cs,
            sizeof(shaders::texture_load_r10g11b11_rgba16_snorm_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR11G11B10ToRGBA16] = std::make_pair(
        shaders::texture_load_r11g11b10_rgba16_scaled_cs,
        sizeof(shaders::texture_load_r11g11b10_rgba16_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR11G11B10ToRGBA16SNorm] =
        std::make_pair(
            shaders::texture_load_r11g11b10_rgba16_snorm_scaled_cs,
            sizeof(shaders::texture_load_r11g11b10_rgba16_snorm_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR16UNormToFloat] =
        std::make_pair(shaders::texture_load_r16_unorm_float_scaled_cs,
                       sizeof(shaders::texture_load_r16_unorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexR16SNormToFloat] =
        std::make_pair(shaders::texture_load_r16_snorm_float_scaled_cs,
                       sizeof(shaders::texture_load_r16_snorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexRG16UNormToFloat] = std::make_pair(
        shaders::texture_load_rg16_unorm_float_scaled_cs,
        sizeof(shaders::texture_load_rg16_unorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexRG16SNormToFloat] = std::make_pair(
        shaders::texture_load_rg16_snorm_float_scaled_cs,
        sizeof(shaders::texture_load_rg16_snorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexRGBA16UNormToFloat] =
        std::make_pair(
            shaders::texture_load_rgba16_unorm_float_scaled_cs,
            sizeof(shaders::texture_load_rgba16_unorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexRGBA16SNormToFloat] =
        std::make_pair(
            shaders::texture_load_rgba16_snorm_float_scaled_cs,
            sizeof(shaders::texture_load_rgba16_snorm_float_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexDepthUnorm] =
        std::make_pair(shaders::texture_load_depth_unorm_scaled_cs,
                       sizeof(shaders::texture_load_depth_unorm_scaled_cs));
    load_shader_code_scaled[kLoadShaderIndexDepthFloat] =
        std::make_pair(shaders::texture_load_depth_float_scaled_cs,
                       sizeof(shaders::texture_load_depth_float_scaled_cs));
  }

  for (size_t i = 0; i < kLoadShaderCount; ++i) {
    if (!load_shaders_needed[i]) {
      continue;
    }
    const std::pair<const uint32_t*, size_t>& current_load_shader_code =
        load_shader_code[i];
    assert_not_null(current_load_shader_code.first);
    load_pipelines_[i] = ui::vulkan::util::CreateComputePipeline(
        vulkan_device, load_pipeline_layout_, current_load_shader_code.first,
        current_load_shader_code.second);
    if (load_pipelines_[i] == VK_NULL_HANDLE) {
      XELOGE(
          "VulkanTextureCache: Failed to create the texture loading pipeline "
          "for shader {}",
          i);
      return false;
    }
    if (IsDrawResolutionScaled()) {
      const std::pair<const uint32_t*, size_t>&
          current_load_shader_code_scaled = load_shader_code_scaled[i];
      if (current_load_shader_code_scaled.first) {
        load_pipelines_scaled_[i] = ui::vulkan::util::CreateComputePipeline(
            vulkan_device, load_pipeline_layout_,
            current_load_shader_code_scaled.first,
            current_load_shader_code_scaled.second);
        if (load_pipelines_scaled_[i] == VK_NULL_HANDLE) {
          XELOGE(
              "VulkanTextureCache: Failed to create the resolution-scaled "
              "texture loading pipeline for shader {}",
              i);
          return false;
        }
      }
    }
  }

  // Null images as a replacement for unneeded bindings and for bindings for
  // which the real image hasn't been created.
  // TODO(Triang3l): Use VK_EXT_robustness2 null descriptors.

  VkImageCreateInfo null_image_create_info;
  null_image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
  null_image_create_info.pNext = nullptr;
  null_image_create_info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
  null_image_create_info.imageType = VK_IMAGE_TYPE_2D;
  // Four components to return (0, 0, 0, 0).
  // TODO(Triang3l): Find the return value for invalid texture fetch constants
  // on the real hardware.
  null_image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
  null_image_create_info.extent.width = 1;
  null_image_create_info.extent.height = 1;
  null_image_create_info.extent.depth = 1;
  null_image_create_info.mipLevels = 1;
  null_image_create_info.arrayLayers = 6;
  null_image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
  null_image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
  null_image_create_info.usage =
      VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
  null_image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
  null_image_create_info.queueFamilyIndexCount = 0;
  null_image_create_info.pQueueFamilyIndices = nullptr;
  null_image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
  if (dfn.vkCreateImage(device, &null_image_create_info, nullptr,
                        &null_image_2d_array_cube_) != VK_SUCCESS) {
    XELOGE(
        "VulkanTextureCache: Failed to create the null 2D array and cube "
        "image");
    return false;
  }

  null_image_create_info.flags &= ~VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
  null_image_create_info.imageType = VK_IMAGE_TYPE_3D;
  null_image_create_info.arrayLayers = 1;
  if (dfn.vkCreateImage(device, &null_image_create_info, nullptr,
                        &null_image_3d_) != VK_SUCCESS) {
    XELOGE("VulkanTextureCache: Failed to create the null 3D image");
    return false;
  }

  VkMemoryRequirements null_image_memory_requirements_2d_array_cube_;
  dfn.vkGetImageMemoryRequirements(
      device, null_image_2d_array_cube_,
      &null_image_memory_requirements_2d_array_cube_);
  VkMemoryRequirements null_image_memory_requirements_3d_;
  dfn.vkGetImageMemoryRequirements(device, null_image_3d_,
                                   &null_image_memory_requirements_3d_);
  uint32_t null_image_memory_type_common = ui::vulkan::util::ChooseMemoryType(
      vulkan_device->memory_types(),
      null_image_memory_requirements_2d_array_cube_.memoryTypeBits &
          null_image_memory_requirements_3d_.memoryTypeBits,
      ui::vulkan::util::MemoryPurpose::kDeviceLocal);
  if (null_image_memory_type_common != UINT32_MAX) {
    // Place both null images in one memory allocation because maximum total
    // memory allocation count is limited.
    VkDeviceSize null_image_memory_offset_3d_ =
        xe::align(null_image_memory_requirements_2d_array_cube_.size,
                  std::max(null_image_memory_requirements_3d_.alignment,
                           VkDeviceSize(1)));
    VkMemoryAllocateInfo null_image_memory_allocate_info;
    null_image_memory_allocate_info.sType =
        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    null_image_memory_allocate_info.pNext = nullptr;
    null_image_memory_allocate_info.allocationSize =
        null_image_memory_offset_3d_ + null_image_memory_requirements_3d_.size;
    null_image_memory_allocate_info.memoryTypeIndex =
        null_image_memory_type_common;
    if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
                             &null_images_memory_[0]) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to allocate the memory for the null "
          "images");
      return false;
    }
    if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_,
                              null_images_memory_[0], 0) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to bind the memory to the null 2D array "
          "and cube image");
      return false;
    }
    if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[0],
                              null_image_memory_offset_3d_) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to bind the memory to the null 3D image");
      return false;
    }
  } else {
    // Place each null image in separate allocations.
    const uint32_t null_image_memory_type_2d_array_cube =
        ui::vulkan::util::ChooseMemoryType(
            vulkan_device->memory_types(),
            null_image_memory_requirements_2d_array_cube_.memoryTypeBits,
            ui::vulkan::util::MemoryPurpose::kDeviceLocal);
    const uint32_t null_image_memory_type_3d =
        ui::vulkan::util::ChooseMemoryType(
            vulkan_device->memory_types(),
            null_image_memory_requirements_3d_.memoryTypeBits,
            ui::vulkan::util::MemoryPurpose::kDeviceLocal);
    if (null_image_memory_type_2d_array_cube == UINT32_MAX ||
        null_image_memory_type_3d == UINT32_MAX) {
      XELOGE(
          "VulkanTextureCache: Failed to get the memory types for the null "
          "images");
      return false;
    }

    VkMemoryAllocateInfo null_image_memory_allocate_info;
    VkMemoryAllocateInfo* null_image_memory_allocate_info_last =
        &null_image_memory_allocate_info;
    null_image_memory_allocate_info.sType =
        VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
    null_image_memory_allocate_info.pNext = nullptr;
    null_image_memory_allocate_info.allocationSize =
        null_image_memory_requirements_2d_array_cube_.size;
    null_image_memory_allocate_info.memoryTypeIndex =
        null_image_memory_type_2d_array_cube;
    VkMemoryDedicatedAllocateInfo null_image_memory_dedicated_allocate_info;
    if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
      null_image_memory_allocate_info_last->pNext =
          &null_image_memory_dedicated_allocate_info;
      null_image_memory_allocate_info_last =
          reinterpret_cast<VkMemoryAllocateInfo*>(
              &null_image_memory_dedicated_allocate_info);
      null_image_memory_dedicated_allocate_info.sType =
          VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
      null_image_memory_dedicated_allocate_info.pNext = nullptr;
      null_image_memory_dedicated_allocate_info.image =
          null_image_2d_array_cube_;
      null_image_memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
    }
    if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
                             &null_images_memory_[0]) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to allocate the memory for the null 2D "
          "array and cube image");
      return false;
    }
    if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_,
                              null_images_memory_[0], 0) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to bind the memory to the null 2D array "
          "and cube image");
      return false;
    }

    null_image_memory_allocate_info.allocationSize =
        null_image_memory_requirements_3d_.size;
    null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_3d;
    null_image_memory_dedicated_allocate_info.image = null_image_3d_;
    if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
                             &null_images_memory_[1]) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to allocate the memory for the null 3D "
          "image");
      return false;
    }
    if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[1],
                              0) != VK_SUCCESS) {
      XELOGE(
          "VulkanTextureCache: Failed to bind the memory to the null 3D image");
      return false;
    }
  }

  VkImageViewCreateInfo null_image_view_create_info;
  null_image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
  null_image_view_create_info.pNext = nullptr;
  null_image_view_create_info.flags = 0;
  null_image_view_create_info.image = null_image_2d_array_cube_;
  null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
  null_image_view_create_info.format = null_image_create_info.format;
  // TODO(Triang3l): Find the return value for invalid texture fetch constants
  // on the real hardware.
  // Micro-optimization if this has any effect on the host GPU at all, use only
  // constant components instead of the real texels. The image will be cleared
  // to (0, 0, 0, 0) anyway.
  VkComponentSwizzle null_image_view_swizzle =
      device_properties.imageViewFormatSwizzle ? VK_COMPONENT_SWIZZLE_ZERO
                                               : VK_COMPONENT_SWIZZLE_IDENTITY;
  null_image_view_create_info.components.r = null_image_view_swizzle;
  null_image_view_create_info.components.g = null_image_view_swizzle;
  null_image_view_create_info.components.b = null_image_view_swizzle;
  null_image_view_create_info.components.a = null_image_view_swizzle;
  null_image_view_create_info.subresourceRange =
      ui::vulkan::util::InitializeSubresourceRange(
          VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, 1);
  if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
                            &null_image_view_2d_array_) != VK_SUCCESS) {
    XELOGE("VulkanTextureCache: Failed to create the null 2D array image view");
    return false;
  }
  null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
  null_image_view_create_info.subresourceRange.layerCount = 6;
  if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
                            &null_image_view_cube_) != VK_SUCCESS) {
    XELOGE("VulkanTextureCache: Failed to create the null cube image view");
    return false;
  }
  null_image_view_create_info.image = null_image_3d_;
  null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
  null_image_view_create_info.subresourceRange.layerCount = 1;
  if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
                            &null_image_view_3d_) != VK_SUCCESS) {
    XELOGE("VulkanTextureCache: Failed to create the null 3D image view");
    return false;
  }

  null_images_cleared_ = false;

  // Samplers.

  // Some MoltenVK devices have a maximum of 2048, 1024, or even 96 samplers,
  // below Vulkan's minimum requirement of 4000.
  // Assuming that the current VulkanTextureCache is the only one on this
  // VkDevice (true in a regular emulation scenario), so taking over all the
  // allocation slots exclusively.
  // Also leaving a few slots for use by things like overlay applications.
  sampler_max_count_ = device_properties.maxSamplerAllocationCount -
                       ui::vulkan::UISamplers::kSamplerCount - 16;

  if (device_properties.samplerAnisotropy) {
    max_anisotropy_ = xenos::AnisoFilter(
        uint32_t(xenos::AnisoFilter::kMax_1_1) +
        (31 -
         xe::lzcnt(uint32_t(std::min(
             16.0f, std::max(1.0f, device_properties.maxSamplerAnisotropy))))));
  } else {
    max_anisotropy_ = xenos::AnisoFilter::kDisabled;
  }

  // Initialize sparse scaled resolve buffers if draw resolution scaling is
  // enabled and sparse binding is supported.
  if (!InitializeSparseScaledResolve()) {
    return false;
  }

  return true;
}

const VulkanTextureCache::HostFormatPair& VulkanTextureCache::GetHostFormatPair(
    TextureKey key) const {
  if (key.format == xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP &&
      (key.GetWidth() & 1)) {
    return kHostFormatGBGRUnaligned;
  }
  if (key.format == xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP &&
      (key.GetWidth() & 1)) {
    return kHostFormatBGRGUnaligned;
  }
  return host_formats_[uint32_t(key.format)];
}

void VulkanTextureCache::GetTextureUsageMasks(VulkanTexture::Usage usage,
                                              VkPipelineStageFlags& stage_mask,
                                              VkAccessFlags& access_mask,
                                              VkImageLayout& layout) {
  stage_mask = 0;
  access_mask = 0;
  layout = VK_IMAGE_LAYOUT_UNDEFINED;
  switch (usage) {
    case VulkanTexture::Usage::kUndefined:
      // For UNDEFINED layout, use TOP_OF_PIPE as source stage (wait for
      // nothing) with no access mask (discarding old contents).
      stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
      break;
    case VulkanTexture::Usage::kTransferDestination:
      stage_mask = VK_PIPELINE_STAGE_TRANSFER_BIT;
      access_mask = VK_ACCESS_TRANSFER_WRITE_BIT;
      layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
      break;
    case VulkanTexture::Usage::kGuestShaderSampled:
      stage_mask = guest_shader_pipeline_stages_;
      access_mask = VK_ACCESS_SHADER_READ_BIT;
      layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
      break;
    case VulkanTexture::Usage::kSwapSampled:
      // The swap texture is likely to be used only for the presentation
      // fragment shader, and not during emulation, where it'd be used in other
      // stages.
      stage_mask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
      access_mask = VK_ACCESS_SHADER_READ_BIT;
      layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
      break;
  }
}

xenos::ClampMode VulkanTextureCache::NormalizeClampMode(
    xenos::ClampMode clamp_mode) const {
  if (clamp_mode == xenos::ClampMode::kClampToHalfway) {
    // No GL_CLAMP (clamp to half edge, half border) equivalent in Vulkan, but
    // there's no Direct3D 9 equivalent anyway, and too weird to be suitable for
    // intentional real usage.
    return xenos::ClampMode::kClampToEdge;
  }
  if (clamp_mode == xenos::ClampMode::kMirrorClampToEdge ||
      clamp_mode == xenos::ClampMode::kMirrorClampToHalfway ||
      clamp_mode == xenos::ClampMode::kMirrorClampToBorder) {
    // No equivalents for anything other than kMirrorClampToEdge in Vulkan.
    return command_processor_.GetVulkanDevice()
                   ->properties()
                   .samplerMirrorClampToEdge
               ? xenos::ClampMode::kMirrorClampToEdge
               : xenos::ClampMode::kMirroredRepeat;
  }
  return clamp_mode;
}

bool VulkanTextureCache::EnsureScaledResolveMemoryCommitted(
    uint32_t start_unscaled, uint32_t length_unscaled,
    uint32_t length_scaled_alignment_log2) {
  if (!IsDrawResolutionScaled()) {
    return true;
  }

  // Dispatch to sparse implementation if supported
  if (sparse_scaled_resolve_supported_) {
    return EnsureScaledResolveMemoryCommittedSparse(
        start_unscaled, length_unscaled, length_scaled_alignment_log2);
  }

  // Simple non-overlapping buffer implementation (fallback)
  if (length_unscaled == 0) {
    return true;
  }

  if (start_unscaled > SharedMemory::kBufferSize ||
      (SharedMemory::kBufferSize - start_unscaled) < length_unscaled) {
    return false;
  }

  uint32_t draw_resolution_scale_area =
      draw_resolution_scale_x() * draw_resolution_scale_y();
  uint64_t start_scaled = uint64_t(start_unscaled) * draw_resolution_scale_area;
  uint64_t length_scaled_alignment_bits =
      (UINT64_C(1) << length_scaled_alignment_log2) - 1;
  uint64_t length_scaled =
      (uint64_t(length_unscaled) * draw_resolution_scale_area +
       length_scaled_alignment_bits) &
      ~length_scaled_alignment_bits;

  // Check if any existing buffer covers this range

  bool range_covered = false;
  for (const ScaledResolveBuffer& buffer : scaled_resolve_buffers_) {
    if (buffer.range_start_scaled <= start_scaled &&
        (buffer.range_start_scaled + buffer.range_length_scaled) >=
            (start_scaled + length_scaled)) {
      // This buffer covers the requested range
      scaled_resolve_current_range_start_scaled_ = buffer.range_start_scaled;
      scaled_resolve_current_range_length_scaled_ = buffer.range_length_scaled;
      range_covered = true;
      break;
    }
  }

  if (!range_covered) {
    // Need to create a new buffer or extend an existing one
    // For simplicity and to avoid fragmentation, we'll use a fixed-size buffer
    // approach similar to D3D12 (but smaller - 256MB chunks instead of 2GB)
    constexpr uint64_t kBufferSize = 256 * 1024 * 1024;  // 256MB per buffer

    // Round up the range to cover complete buffer chunks
    uint64_t buffer_start = (start_scaled / kBufferSize) * kBufferSize;
    uint64_t buffer_end =
        ((start_scaled + length_scaled + kBufferSize - 1) / kBufferSize) *
        kBufferSize;
    uint64_t buffer_size = buffer_end - buffer_start;

    // Check again if this expanded range is covered
    bool expanded_range_covered = false;
    for (const ScaledResolveBuffer& buffer : scaled_resolve_buffers_) {
      if (buffer.range_start_scaled <= buffer_start &&
          (buffer.range_start_scaled + buffer.range_length_scaled) >=
              buffer_end) {
        scaled_resolve_current_range_start_scaled_ = buffer.range_start_scaled;
        scaled_resolve_current_range_length_scaled_ =
            buffer.range_length_scaled;
        expanded_range_covered = true;
        break;
      }
    }

    if (!expanded_range_covered) {
      // Limit the number of buffers to prevent unbounded growth
      constexpr size_t kMaxBuffers = 32;  // Maximum 8GB total (32 * 256MB)
      if (scaled_resolve_buffers_.size() >= kMaxBuffers) {
        // Reuse the least recently used buffer
        // For now, just reuse the first buffer (simple LRU would be better)
        ScaledResolveBuffer& reused_buffer = scaled_resolve_buffers_[0];
        reused_buffer.range_start_scaled = buffer_start;
        reused_buffer.range_length_scaled = buffer_size;
        scaled_resolve_current_range_start_scaled_ = buffer_start;
        scaled_resolve_current_range_length_scaled_ = buffer_size;
      } else {
        ScaledResolveBuffer new_buffer;
        new_buffer.size = buffer_size;

        VkBufferCreateInfo buffer_create_info = {};
        buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
        buffer_create_info.size = new_buffer.size;
        buffer_create_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
        buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

        VmaAllocationCreateInfo allocation_create_info = {};
        allocation_create_info.usage = VMA_MEMORY_USAGE_GPU_ONLY;

        VkResult result = vmaCreateBuffer(
            vma_allocator_, &buffer_create_info, &allocation_create_info,
            &new_buffer.buffer, &new_buffer.allocation, nullptr);

        if (result != VK_SUCCESS) {
          XELOGE(
              "VulkanTextureCache: Failed to create scaled resolve buffer: {}",
              static_cast<int>(result));
          return false;
        }

        new_buffer.range_start_scaled = buffer_start;
        new_buffer.range_length_scaled = buffer_size;

        scaled_resolve_buffers_.push_back(new_buffer);
        scaled_resolve_current_range_start_scaled_ = buffer_start;
        scaled_resolve_current_range_length_scaled_ = buffer_size;
      }
    }
  }

  return true;
}

bool VulkanTextureCache::MakeScaledResolveRangeCurrent(
    uint32_t start_unscaled, uint32_t length_unscaled,
    uint32_t length_scaled_alignment_log2) {
  if (!IsDrawResolutionScaled()) {
    return false;
  }

  // Dispatch to sparse implementation if supported
  if (sparse_scaled_resolve_supported_) {
    return MakeScaledResolveRangeCurrentSparse(start_unscaled, length_unscaled,
                                               length_scaled_alignment_log2);
  }

  // Simple non-overlapping buffer implementation (fallback)
  // First ensure the memory is committed (creates buffers if needed)
  if (!EnsureScaledResolveMemoryCommitted(start_unscaled, length_unscaled,
                                          length_scaled_alignment_log2)) {
    return false;
  }

  const uint32_t draw_resolution_scale_area =
      draw_resolution_scale_x() * draw_resolution_scale_y();
  const uint64_t start_scaled =
      uint64_t(start_unscaled) * draw_resolution_scale_area;

  const uint64_t length_scaled_alignment_bits =
      (UINT64_C(1) << length_scaled_alignment_log2) - 1;
  const uint64_t length_scaled =
      (uint64_t(length_unscaled) * draw_resolution_scale_area +
       length_scaled_alignment_bits) &
      ~length_scaled_alignment_bits;
  const uint64_t end_scaled = start_scaled + length_scaled;

  // Find which buffer contains this entire range (not just the start)
  for (size_t i = 0; i < scaled_resolve_buffers_.size(); ++i) {
    const ScaledResolveBuffer& buffer = scaled_resolve_buffers_[i];
    if (start_scaled >= buffer.range_start_scaled &&
        end_scaled <=
            (buffer.range_start_scaled + buffer.range_length_scaled)) {
      scaled_resolve_current_buffer_index_ = i;
      return true;
    }
  }

  return false;
}

VkBuffer VulkanTextureCache::GetCurrentScaledResolveBuffer() const {
  if (sparse_scaled_resolve_supported_) {
    if (scaled_resolve_current_buffer_index_ < kMaxScaledResolveSparseBuffers &&
        scaled_resolve_sparse_buffers_[scaled_resolve_current_buffer_index_]) {
      return scaled_resolve_sparse_buffers_
          [scaled_resolve_current_buffer_index_]
              ->buffer();
    }
    return VK_NULL_HANDLE;
  }
  if (scaled_resolve_current_buffer_index_ >= scaled_resolve_buffers_.size()) {
    return VK_NULL_HANDLE;
  }
  return scaled_resolve_buffers_[scaled_resolve_current_buffer_index_].buffer;
}

// Sparse scaled resolve helper functions

size_t VulkanTextureCache::GetScaledResolveSparseBufferCount() const {
  // Each buffer is 2GB, and buffer N covers [N GB .. (N+2) GB)
  // So for an address space of X bytes, we need ceil((X-1) / 1GB) buffers
  // (or 0 if X <= 2GB, but we handle that with max)
  uint64_t address_space = uint64_t(SharedMemory::kBufferSize) *
                           draw_resolution_scale_x() *
                           draw_resolution_scale_y();
  // Number of buffers needed: ceil((address_space - 1) / 1GB)
  // For 3x3 (4.5GB): (4.5GB - 1) / 1GB = 3.5 -> 4 buffers
  // For 2x2 (2GB): (2GB - 1) / 1GB = 0.99 -> 1 buffer
  // For 1x1 (512MB): (512MB - 1) / 1GB = 0 -> but we need at least 1
  if (address_space <= kScaledResolveSparseBufferSize) {
    return 1;
  }
  return size_t((address_space - 1) >> 30);
}

std::array<size_t, 2> VulkanTextureCache::GetPossibleScaledResolveBufferIndices(
    uint64_t address_scaled) const {
  // Given an address, find which buffers could contain it.
  // Buffer N covers [N GB .. (N+2) GB), so address A could be in:
  // - Buffer floor(A/1GB) if it exists (address is in [N GB .. (N+1) GB) part)
  // - Buffer floor(A/1GB) - 1 if it exists (address is in [(N-1)+1 GB .. N+1
  // GB) part)
  size_t gb_index = size_t(address_scaled >> 30);
  size_t buffer_count = GetScaledResolveSparseBufferCount();
  size_t max_buffer_index = buffer_count > 0 ? buffer_count - 1 : 0;

  // First possible buffer: the one starting at this GB
  size_t buffer_a = std::min(gb_index, max_buffer_index);
  // Second possible buffer: the one starting 1GB earlier (if it exists)
  size_t buffer_b = gb_index > 0 ? std::min(gb_index - 1, max_buffer_index)
                                 : max_buffer_index + 1;  // Invalid

  return {buffer_a, buffer_b};
}

bool VulkanTextureCache::InitializeSparseScaledResolve() {
  if (!IsDrawResolutionScaled()) {
    return true;
  }

  if (!cvars::tiled_shared_memory) {
    XELOGI("VulkanTextureCache: Sparse scaled resolve disabled by CVAR");
    return true;
  }

  const ui::vulkan::VulkanDevice* vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Properties& device_properties =
      vulkan_device->properties();

  if (!device_properties.sparseBinding ||
      !device_properties.sparseResidencyBuffer) {
    XELOGI(
        "VulkanTextureCache: Sparse binding not supported, using simple "
        "buffers");
    return true;
  }

  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  VkDevice device = vulkan_device->device();

  size_t buffer_count = GetScaledResolveSparseBufferCount();
  XELOGI(
      "VulkanTextureCache: Creating {} sparse scaled resolve buffers (2GB "
      "each)",
      buffer_count);

  // Create the sparse buffers
  for (size_t i = 0; i < buffer_count; ++i) {
    VkBufferCreateInfo buffer_create_info = {};
    buffer_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
    buffer_create_info.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT |
                               VK_BUFFER_CREATE_SPARSE_RESIDENCY_BIT;
    buffer_create_info.size = kScaledResolveSparseBufferSize;
    buffer_create_info.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
    buffer_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;

    VkBuffer buffer;
    VkResult result =
        dfn.vkCreateBuffer(device, &buffer_create_info, nullptr, &buffer);
    if (result != VK_SUCCESS) {
      XELOGE("VulkanTextureCache: Failed to create sparse buffer {}: error {}",
             i, static_cast<int>(result));
      ShutdownSparseScaledResolve();
      return true;  // Fall back to simple buffers
    }

    scaled_resolve_sparse_buffers_[i] =
        std::make_unique<ScaledResolveSparseBuffer>(buffer);
  }

  // Get memory requirements for the sparse buffers
  VkMemoryRequirements memory_requirements;
  dfn.vkGetBufferMemoryRequirements(device,
                                    scaled_resolve_sparse_buffers_[0]->buffer(),
                                    &memory_requirements);

  // Find a suitable device-local memory type
  if (!xe::bit_scan_forward(memory_requirements.memoryTypeBits &
                                vulkan_device->memory_types().device_local,
                            &scaled_resolve_memory_type_)) {
    XELOGE(
        "VulkanTextureCache: Failed to find suitable memory type for sparse "
        "buffers");
    ShutdownSparseScaledResolve();
    return true;
  }

  // Calculate the number of heaps we might need
  uint64_t address_space = uint64_t(SharedMemory::kBufferSize) *
                           draw_resolution_scale_x() *
                           draw_resolution_scale_y();
  uint32_t max_heap_count =
      uint32_t((address_space + kScaledResolveHeapSize - 1) >>
               kScaledResolveHeapSizeLog2);
  scaled_resolve_heaps_.resize(max_heap_count, VK_NULL_HANDLE);
  scaled_resolve_heap_count_ = 0;

  sparse_scaled_resolve_supported_ = true;
  XELOGI(
      "VulkanTextureCache: Sparse scaled resolve initialized with {} buffers, "
      "max {} heaps",
      buffer_count, max_heap_count);

  return true;
}

void VulkanTextureCache::ShutdownSparseScaledResolve() {
  const ui::vulkan::VulkanDevice* vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  VkDevice device = vulkan_device->device();

  // Free all heaps
  for (VkDeviceMemory heap : scaled_resolve_heaps_) {
    if (heap != VK_NULL_HANDLE) {
      dfn.vkFreeMemory(device, heap, nullptr);
    }
  }
  scaled_resolve_heaps_.clear();
  scaled_resolve_heap_count_ = 0;

  // Destroy sparse buffers
  for (auto& buffer : scaled_resolve_sparse_buffers_) {
    if (buffer) {
      dfn.vkDestroyBuffer(device, buffer->buffer(), nullptr);
      buffer.reset();
    }
  }

  scaled_resolve_memory_type_ = UINT32_MAX;
  sparse_scaled_resolve_supported_ = false;
}

void VulkanTextureCache::BindHeapToOverlappingBuffers(uint32_t heap_index,
                                                      VkDeviceMemory heap) {
  // Calculate the address this heap covers
  uint64_t heap_address = uint64_t(heap_index) << kScaledResolveHeapSizeLog2;

  // Find which buffers this heap should be bound to
  auto [buffer_a, buffer_b] =
      GetPossibleScaledResolveBufferIndices(heap_address);

  size_t buffer_count = GetScaledResolveSparseBufferCount();

  // Bind to the first possible buffer
  if (buffer_a < buffer_count && scaled_resolve_sparse_buffers_[buffer_a]) {
    uint64_t offset_in_buffer =
        heap_address - (uint64_t(buffer_a) << 30);  // buffer_a * 1GB

    VkSparseMemoryBind bind = {};
    bind.resourceOffset = offset_in_buffer;
    bind.size = kScaledResolveHeapSize;
    bind.memory = heap;
    bind.memoryOffset = 0;
    bind.flags = 0;

    command_processor_.SparseBindBuffer(
        scaled_resolve_sparse_buffers_[buffer_a]->buffer(), 1, &bind,
        VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
  }

  // Bind to the second possible buffer (if different and valid)
  if (buffer_b < buffer_count && buffer_b != buffer_a &&
      scaled_resolve_sparse_buffers_[buffer_b]) {
    uint64_t offset_in_buffer =
        heap_address - (uint64_t(buffer_b) << 30);  // buffer_b * 1GB

    VkSparseMemoryBind bind = {};
    bind.resourceOffset = offset_in_buffer;
    bind.size = kScaledResolveHeapSize;
    bind.memory = heap;
    bind.memoryOffset = 0;
    bind.flags = 0;

    command_processor_.SparseBindBuffer(
        scaled_resolve_sparse_buffers_[buffer_b]->buffer(), 1, &bind,
        VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
  }
}

bool VulkanTextureCache::EnsureScaledResolveMemoryCommittedSparse(
    uint32_t start_unscaled, uint32_t length_unscaled,
    uint32_t length_scaled_alignment_log2) {
  if (length_unscaled == 0) {
    return true;
  }

  if (start_unscaled > SharedMemory::kBufferSize ||
      (SharedMemory::kBufferSize - start_unscaled) < length_unscaled) {
    return false;
  }

  uint32_t draw_resolution_scale_area =
      draw_resolution_scale_x() * draw_resolution_scale_y();
  uint64_t start_scaled = uint64_t(start_unscaled) * draw_resolution_scale_area;
  uint64_t length_scaled_alignment_bits =
      (UINT64_C(1) << length_scaled_alignment_log2) - 1;
  uint64_t length_scaled =
      (uint64_t(length_unscaled) * draw_resolution_scale_area +
       length_scaled_alignment_bits) &
      ~length_scaled_alignment_bits;
  uint64_t end_scaled = start_scaled + length_scaled;

  // Calculate which heaps we need
  uint32_t heap_first = uint32_t(start_scaled >> kScaledResolveHeapSizeLog2);
  uint32_t heap_last = uint32_t((end_scaled - 1) >> kScaledResolveHeapSizeLog2);

  const ui::vulkan::VulkanDevice* vulkan_device =
      command_processor_.GetVulkanDevice();
  const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
  VkDevice device = vulkan_device->device();

  // Allocate and bind any heaps we need
  for (uint32_t heap_index = heap_first; heap_index <= heap_last;
       ++heap_index) {
    if (heap_index >= scaled_resolve_heaps_.size()) {
      XELOGE("VulkanTextureCache: Heap index {} out of range (max {})",
             heap_index, scaled_resolve_heaps_.size());
      return false;
    }

    if (scaled_resolve_heaps_[heap_index] == VK_NULL_HANDLE) {
      // Allocate a new heap
      VkMemoryAllocateInfo allocate_info = {};
      allocate_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
      allocate_info.allocationSize = kScaledResolveHeapSize;
      allocate_info.memoryTypeIndex = scaled_resolve_memory_type_;

      VkDeviceMemory heap;
      VkResult result =
          dfn.vkAllocateMemory(device, &allocate_info, nullptr, &heap);
      if (result != VK_SUCCESS) {
        XELOGE("VulkanTextureCache: Failed to allocate heap {}: error {}",
               heap_index, static_cast<int>(result));
        return false;
      }

      scaled_resolve_heaps_[heap_index] = heap;
      ++scaled_resolve_heap_count_;

      // Bind this heap to all overlapping buffers
      BindHeapToOverlappingBuffers(heap_index, heap);
    }
  }

  return true;
}

bool VulkanTextureCache::MakeScaledResolveRangeCurrentSparse(
    uint32_t start_unscaled, uint32_t length_unscaled,
    uint32_t length_scaled_alignment_log2) {
  // First ensure the memory is committed
  if (!EnsureScaledResolveMemoryCommittedSparse(start_unscaled, length_unscaled,
                                                length_scaled_alignment_log2)) {
    return false;
  }

  uint32_t draw_resolution_scale_area =
      draw_resolution_scale_x() * draw_resolution_scale_y();
  uint64_t start_scaled = uint64_t(start_unscaled) * draw_resolution_scale_area;
  uint64_t length_scaled_alignment_bits =
      (UINT64_C(1) << length_scaled_alignment_log2) - 1;
  uint64_t length_scaled =
      (uint64_t(length_unscaled) * draw_resolution_scale_area +
       length_scaled_alignment_bits) &
      ~length_scaled_alignment_bits;
  uint64_t end_scaled = start_scaled + length_scaled;

  // Find a buffer that contains the entire range
  // Each buffer is 2GB and covers [buffer_index GB .. (buffer_index + 2) GB)
  auto [buffer_a, buffer_b] =
      GetPossibleScaledResolveBufferIndices(start_scaled);
  auto [end_buffer_a, end_buffer_b] = GetPossibleScaledResolveBufferIndices(
      end_scaled > 0 ? end_scaled - 1 : 0);

  // Check if buffer_a can contain both start and end
  size_t chosen_buffer = SIZE_MAX;
  if (buffer_a == end_buffer_a || buffer_a == end_buffer_b) {
    chosen_buffer = buffer_a;
  } else if (buffer_b == end_buffer_a || buffer_b == end_buffer_b) {
    chosen_buffer = buffer_b;
  }

  if (chosen_buffer == SIZE_MAX ||
      chosen_buffer >= GetScaledResolveSparseBufferCount()) {
    XELOGE("VulkanTextureCache: No buffer can contain range [{:X}, {:X})",
           start_scaled, end_scaled);
    return false;
  }

  scaled_resolve_current_buffer_index_ = chosen_buffer;
  return true;
}

}  // namespace vulkan
}  // namespace gpu
}  // namespace xe
