/** ****************************************************************************** * 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 #include #include #include #include "xenia/base/assert.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" 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& 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 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(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(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) const { VkImageView image_view = VK_NULL_HANDLE; const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); if (binding && AreDimensionsCompatible(dimension, binding->key.dimension)) { 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& 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& 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* 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* 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(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 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; 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(&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( new VulkanTexture(*this, key, image, allocation)); } bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base, bool load_mips) { VulkanTexture& vulkan_texture = static_cast(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; bool is_3d = dimension == xenos::DataDimension::k3D; 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; assert_true(level_first <= level_last); 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(shared_memory()); std::array 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. // Aligning because if the data for a vector in a storage buffer is provided // partially, the value read may still be (0, 0, 0, 0), and small (especially // linear) textures won't be loaded correctly. uint32_t source_length_alignment = UINT32_C(1) << load_shader_info.source_bpe_log2; 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 = 0; if (scaled_resolve_current_buffer_index_ < scaled_resolve_buffers_.size()) { const ScaledResolveBuffer& current_buffer = scaled_resolve_buffers_[scaled_resolve_current_buffer_index_]; buffer_relative_offset = scaled_offset - current_buffer.range_start_scaled; } write_descriptor_set_source_base_buffer_info.buffer = scaled_buffer; write_descriptor_set_source_base_buffer_info.offset = buffer_relative_offset; write_descriptor_set_source_base_buffer_info.range = xe::align(guest_size * draw_resolution_scale_area, source_length_alignment); } 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; write_descriptor_set_source_base_buffer_info.range = xe::align(vulkan_texture.GetGuestBaseSize(), source_length_alignment); } 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; } if (level_last != 0) { descriptor_set_source_mips = command_processor_.AllocateSingleTransientDescriptor( VulkanCommandProcessor::SingleTransientDescriptorLayout :: kStorageBufferCompute); if (!descriptor_set_source_mips) { return false; } // TODO: Implement scaled mips support similar to D3D12. // Currently mips are always loaded from unscaled shared memory even when // the base texture is scaled. D3D12 properly handles scaled mips in // D3D12TextureCache::LoadTextureDataFromResidentMemoryImpl. 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; write_descriptor_set_source_mips_buffer_info.range = xe::align(vulkan_texture.GetGuestMipsSize(), source_length_alignment); 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) << 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)); } 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(binding->texture) ->GetView(false, binding->host_swizzle); } if (binding->texture_signed && texture_util::IsAnySignSigned(binding->swizzled_signs)) { vulkan_binding.image_view_signed = static_cast(binding->texture_signed) ->GetView(true, binding->host_swizzle); } } else { VulkanTexture* texture = static_cast(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) : Texture(texture_cache, key), 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(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); } 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(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; } 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 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 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& 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& 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( &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; } 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: 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; } 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(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; } // 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 (scaled_resolve_current_buffer_index_ >= scaled_resolve_buffers_.size()) { return VK_NULL_HANDLE; } return scaled_resolve_buffers_[scaled_resolve_current_buffer_index_].buffer; } } // namespace vulkan } // namespace gpu } // namespace xe