2905 lines
131 KiB
C++
2905 lines
131 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2022 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#include "xenia/gpu/vulkan/vulkan_texture_cache.h"
|
|
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <cstddef>
|
|
#include <utility>
|
|
|
|
#include "xenia/base/assert.h"
|
|
#include "xenia/base/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<const SamplerParameters, Sampler>& sampler_pair :
|
|
samplers_) {
|
|
dfn.vkDestroySampler(device, sampler_pair.second.sampler, nullptr);
|
|
}
|
|
samplers_.clear();
|
|
COUNT_profile_set("gpu/texture_cache/vulkan/samplers", 0);
|
|
sampler_used_last_ = nullptr;
|
|
sampler_used_first_ = nullptr;
|
|
|
|
if (null_image_view_3d_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, null_image_view_3d_, nullptr);
|
|
}
|
|
if (null_image_view_cube_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, null_image_view_cube_, nullptr);
|
|
}
|
|
if (null_image_view_2d_array_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImageView(device, null_image_view_2d_array_, nullptr);
|
|
}
|
|
if (null_image_3d_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImage(device, null_image_3d_, nullptr);
|
|
}
|
|
if (null_image_2d_array_cube_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyImage(device, null_image_2d_array_cube_, nullptr);
|
|
}
|
|
for (VkDeviceMemory null_images_memory : null_images_memory_) {
|
|
if (null_images_memory != VK_NULL_HANDLE) {
|
|
dfn.vkFreeMemory(device, null_images_memory, nullptr);
|
|
}
|
|
}
|
|
for (VkPipeline load_pipeline : load_pipelines_scaled_) {
|
|
if (load_pipeline != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyPipeline(device, load_pipeline, nullptr);
|
|
}
|
|
}
|
|
for (VkPipeline load_pipeline : load_pipelines_) {
|
|
if (load_pipeline != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyPipeline(device, load_pipeline, nullptr);
|
|
}
|
|
}
|
|
if (load_pipeline_layout_ != VK_NULL_HANDLE) {
|
|
dfn.vkDestroyPipelineLayout(device, load_pipeline_layout_, nullptr);
|
|
}
|
|
|
|
// Textures memory is allocated using the Vulkan Memory Allocator, destroy all
|
|
// textures before destroying VMA.
|
|
DestroyAllTextures(true);
|
|
|
|
// Clean up scaled resolve buffers before destroying VMA
|
|
// The command processor should ensure all GPU operations are complete
|
|
// before the texture cache is destroyed
|
|
for (ScaledResolveBuffer& buffer : scaled_resolve_buffers_) {
|
|
if (buffer.buffer != VK_NULL_HANDLE) {
|
|
vmaDestroyBuffer(vma_allocator_, buffer.buffer, buffer.allocation);
|
|
}
|
|
}
|
|
scaled_resolve_buffers_.clear();
|
|
|
|
if (vma_allocator_ != VK_NULL_HANDLE) {
|
|
vmaDestroyAllocator(vma_allocator_);
|
|
}
|
|
}
|
|
|
|
void VulkanTextureCache::BeginSubmission(uint64_t new_submission_index) {
|
|
TextureCache::BeginSubmission(new_submission_index);
|
|
|
|
if (!null_images_cleared_) {
|
|
VkImage null_images[] = {null_image_2d_array_cube_, null_image_3d_};
|
|
VkImageSubresourceRange null_image_subresource_range(
|
|
ui::vulkan::util::InitializeSubresourceRange());
|
|
for (size_t i = 0; i < xe::countof(null_images); ++i) {
|
|
command_processor_.PushImageMemoryBarrier(
|
|
null_images[i], null_image_subresource_range, 0,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, false);
|
|
}
|
|
command_processor_.SubmitBarriers(true);
|
|
DeferredCommandBuffer& command_buffer =
|
|
command_processor_.deferred_command_buffer();
|
|
// TODO(Triang3l): Find the return value for invalid texture fetch constants
|
|
// on the real hardware.
|
|
VkClearColorValue null_image_clear_color;
|
|
null_image_clear_color.float32[0] = 0.0f;
|
|
null_image_clear_color.float32[1] = 0.0f;
|
|
null_image_clear_color.float32[2] = 0.0f;
|
|
null_image_clear_color.float32[3] = 0.0f;
|
|
for (size_t i = 0; i < xe::countof(null_images); ++i) {
|
|
command_buffer.CmdVkClearColorImage(
|
|
null_images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
&null_image_clear_color, 1, &null_image_subresource_range);
|
|
}
|
|
for (size_t i = 0; i < xe::countof(null_images); ++i) {
|
|
command_processor_.PushImageMemoryBarrier(
|
|
null_images[i], null_image_subresource_range,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, guest_shader_pipeline_stages_,
|
|
VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT,
|
|
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
|
|
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_QUEUE_FAMILY_IGNORED,
|
|
VK_QUEUE_FAMILY_IGNORED, false);
|
|
}
|
|
null_images_cleared_ = true;
|
|
}
|
|
}
|
|
|
|
void VulkanTextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|
#if XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
SCOPE_profile_cpu_f("gpu");
|
|
#endif // XE_GPU_FINE_GRAINED_DRAW_SCOPES
|
|
|
|
TextureCache::RequestTextures(used_texture_mask);
|
|
|
|
// Transition the textures into the needed usage.
|
|
VkPipelineStageFlags dst_stage_mask;
|
|
VkAccessFlags dst_access_mask;
|
|
VkImageLayout new_layout;
|
|
GetTextureUsageMasks(VulkanTexture::Usage::kGuestShaderSampled,
|
|
dst_stage_mask, dst_access_mask, new_layout);
|
|
uint32_t textures_remaining = used_texture_mask;
|
|
uint32_t index;
|
|
while (xe::bit_scan_forward(textures_remaining, &index)) {
|
|
textures_remaining &= ~(uint32_t(1) << index);
|
|
const TextureBinding* binding = GetValidTextureBinding(index);
|
|
if (!binding) {
|
|
continue;
|
|
}
|
|
VulkanTexture* binding_texture =
|
|
static_cast<VulkanTexture*>(binding->texture);
|
|
if (binding_texture != nullptr) {
|
|
// Will be referenced by the command buffer, so mark as used.
|
|
binding_texture->MarkAsUsed();
|
|
VulkanTexture::Usage old_usage =
|
|
binding_texture->SetUsage(VulkanTexture::Usage::kGuestShaderSampled);
|
|
if (old_usage != VulkanTexture::Usage::kGuestShaderSampled) {
|
|
VkPipelineStageFlags src_stage_mask;
|
|
VkAccessFlags src_access_mask;
|
|
VkImageLayout old_layout;
|
|
GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
|
|
old_layout);
|
|
command_processor_.PushImageMemoryBarrier(
|
|
binding_texture->image(),
|
|
ui::vulkan::util::InitializeSubresourceRange(), src_stage_mask,
|
|
dst_stage_mask, src_access_mask, dst_access_mask, old_layout,
|
|
new_layout);
|
|
}
|
|
}
|
|
VulkanTexture* binding_texture_signed =
|
|
static_cast<VulkanTexture*>(binding->texture_signed);
|
|
if (binding_texture_signed != nullptr) {
|
|
binding_texture_signed->MarkAsUsed();
|
|
VulkanTexture::Usage old_usage = binding_texture_signed->SetUsage(
|
|
VulkanTexture::Usage::kGuestShaderSampled);
|
|
if (old_usage != VulkanTexture::Usage::kGuestShaderSampled) {
|
|
VkPipelineStageFlags src_stage_mask;
|
|
VkAccessFlags src_access_mask;
|
|
VkImageLayout old_layout;
|
|
GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
|
|
old_layout);
|
|
command_processor_.PushImageMemoryBarrier(
|
|
binding_texture_signed->image(),
|
|
ui::vulkan::util::InitializeSubresourceRange(), src_stage_mask,
|
|
dst_stage_mask, src_access_mask, dst_access_mask, old_layout,
|
|
new_layout);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VkImageView VulkanTextureCache::GetActiveBindingOrNullImageView(
|
|
uint32_t fetch_constant_index, xenos::FetchOpDimension dimension,
|
|
bool is_signed) 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<const SamplerParameters, Sampler>& sampler = *it_existing;
|
|
assert_true(sampler.second.last_usage_submission <= submission_current);
|
|
// This is called very frequently, don't relink unless needed for caching.
|
|
if (sampler.second.last_usage_submission < submission_current) {
|
|
// Move to the front of the LRU queue.
|
|
sampler.second.last_usage_submission = submission_current;
|
|
if (sampler.second.used_next) {
|
|
if (sampler.second.used_previous) {
|
|
sampler.second.used_previous->second.used_next =
|
|
sampler.second.used_next;
|
|
} else {
|
|
sampler_used_first_ = sampler.second.used_next;
|
|
}
|
|
sampler.second.used_next->second.used_previous =
|
|
sampler.second.used_previous;
|
|
sampler.second.used_previous = sampler_used_last_;
|
|
sampler.second.used_next = nullptr;
|
|
sampler_used_last_->second.used_next = &sampler;
|
|
sampler_used_last_ = &sampler;
|
|
}
|
|
}
|
|
has_overflown_out = false;
|
|
return sampler.second.sampler;
|
|
}
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
command_processor_.GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
// See if an existing sampler can be destroyed to create space for the new
|
|
// one.
|
|
if (samplers_.size() >= sampler_max_count_) {
|
|
assert_not_null(sampler_used_first_);
|
|
if (!sampler_used_first_) {
|
|
has_overflown_out = false;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
if (sampler_used_first_->second.last_usage_submission >
|
|
command_processor_.GetCompletedSubmission()) {
|
|
has_overflown_out = true;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
auto it_reuse = samplers_.find(sampler_used_first_->first);
|
|
dfn.vkDestroySampler(device, sampler_used_first_->second.sampler, nullptr);
|
|
if (sampler_used_first_->second.used_next) {
|
|
sampler_used_first_->second.used_next->second.used_previous =
|
|
sampler_used_first_->second.used_previous;
|
|
} else {
|
|
sampler_used_last_ = sampler_used_first_->second.used_previous;
|
|
}
|
|
sampler_used_first_ = sampler_used_first_->second.used_next;
|
|
assert_true(it_reuse != samplers_.end());
|
|
if (it_reuse != samplers_.end()) {
|
|
// This destroys the Sampler object.
|
|
samplers_.erase(it_reuse);
|
|
COUNT_profile_set("gpu/texture_cache/vulkan/samplers", samplers_.size());
|
|
} else {
|
|
has_overflown_out = false;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
// Create a new sampler and make it the least recently used.
|
|
// The values are normalized, and unsupported ones are excluded, in
|
|
// GetSamplerParameters.
|
|
VkSamplerCreateInfo sampler_create_info = {};
|
|
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
|
if (vulkan_device->properties().nonSeamlessCubeMap &&
|
|
cvars::non_seamless_cube_map) {
|
|
sampler_create_info.flags |=
|
|
VK_SAMPLER_CREATE_NON_SEAMLESS_CUBE_MAP_BIT_EXT;
|
|
}
|
|
sampler_create_info.magFilter =
|
|
parameters.mag_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
|
sampler_create_info.minFilter =
|
|
parameters.min_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST;
|
|
sampler_create_info.mipmapMode = parameters.mip_linear
|
|
? VK_SAMPLER_MIPMAP_MODE_LINEAR
|
|
: VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
static constexpr VkSamplerAddressMode kAddressModeMap[] = {
|
|
// kRepeat
|
|
VK_SAMPLER_ADDRESS_MODE_REPEAT,
|
|
// kMirroredRepeat
|
|
VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
|
|
// kClampToEdge
|
|
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
|
// kMirrorClampToEdge
|
|
VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
|
|
// kClampToHalfway
|
|
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
|
|
// kMirrorClampToHalfway
|
|
VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
|
|
// kClampToBorder
|
|
VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER,
|
|
// kMirrorClampToBorder
|
|
VK_SAMPLER_ADDRESS_MODE_MIRROR_CLAMP_TO_EDGE,
|
|
};
|
|
sampler_create_info.addressModeU =
|
|
kAddressModeMap[uint32_t(parameters.clamp_x)];
|
|
sampler_create_info.addressModeV =
|
|
kAddressModeMap[uint32_t(parameters.clamp_y)];
|
|
sampler_create_info.addressModeW =
|
|
kAddressModeMap[uint32_t(parameters.clamp_z)];
|
|
// LOD biasing is performed in shaders.
|
|
if (parameters.aniso_filter != xenos::AnisoFilter::kDisabled) {
|
|
sampler_create_info.anisotropyEnable = VK_TRUE;
|
|
sampler_create_info.maxAnisotropy =
|
|
float(UINT32_C(1) << (uint32_t(parameters.aniso_filter) -
|
|
uint32_t(xenos::AnisoFilter::kMax_1_1)));
|
|
}
|
|
sampler_create_info.minLod = float(parameters.mip_min_level);
|
|
if (parameters.mip_base_map) {
|
|
assert_false(parameters.mip_linear);
|
|
sampler_create_info.maxLod = sampler_create_info.minLod + 0.25f;
|
|
} else {
|
|
sampler_create_info.maxLod = VK_LOD_CLAMP_NONE;
|
|
}
|
|
// TODO(Triang3l): Custom border colors for CrYCb / YCrCb.
|
|
switch (parameters.border_color) {
|
|
case xenos::BorderColor::k_ABGR_White:
|
|
sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
break;
|
|
default:
|
|
sampler_create_info.borderColor = VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK;
|
|
break;
|
|
}
|
|
VkSampler vulkan_sampler;
|
|
if (dfn.vkCreateSampler(device, &sampler_create_info, nullptr,
|
|
&vulkan_sampler) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to create the sampler for parameters "
|
|
"0x{:08X}",
|
|
parameters.value);
|
|
has_overflown_out = false;
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
std::pair<const SamplerParameters, Sampler>& new_sampler =
|
|
*(samplers_
|
|
.emplace(std::piecewise_construct,
|
|
std::forward_as_tuple(parameters), std::forward_as_tuple())
|
|
.first);
|
|
COUNT_profile_set("gpu/texture_cache/vulkan/samplers", samplers_.size());
|
|
new_sampler.second.sampler = vulkan_sampler;
|
|
new_sampler.second.last_usage_submission = submission_current;
|
|
new_sampler.second.used_previous = sampler_used_last_;
|
|
new_sampler.second.used_next = nullptr;
|
|
if (sampler_used_last_) {
|
|
sampler_used_last_->second.used_next = &new_sampler;
|
|
} else {
|
|
sampler_used_first_ = &new_sampler;
|
|
}
|
|
sampler_used_last_ = &new_sampler;
|
|
return vulkan_sampler;
|
|
}
|
|
|
|
uint64_t VulkanTextureCache::GetSubmissionToAwaitOnSamplerOverflow(
|
|
uint32_t overflowed_sampler_count) const {
|
|
if (!overflowed_sampler_count) {
|
|
return 0;
|
|
}
|
|
std::pair<const SamplerParameters, Sampler>* sampler_used =
|
|
sampler_used_first_;
|
|
if (!sampler_used_first_) {
|
|
return 0;
|
|
}
|
|
for (uint32_t samplers_remaining = overflowed_sampler_count - 1;
|
|
samplers_remaining; --samplers_remaining) {
|
|
std::pair<const SamplerParameters, Sampler>* sampler_used_next =
|
|
sampler_used->second.used_next;
|
|
if (!sampler_used_next) {
|
|
break;
|
|
}
|
|
sampler_used = sampler_used_next;
|
|
}
|
|
return sampler_used->second.last_usage_submission;
|
|
}
|
|
|
|
VkImageView VulkanTextureCache::RequestSwapTexture(
|
|
uint32_t& width_scaled_out, uint32_t& height_scaled_out,
|
|
xenos::TextureFormat& format_out) {
|
|
const auto& regs = register_file();
|
|
xenos::xe_gpu_texture_fetch_t fetch = regs.GetTextureFetch(0);
|
|
TextureKey key;
|
|
BindingInfoFromFetchConstant(fetch, key, nullptr);
|
|
if (!key.is_valid || key.base_page == 0 ||
|
|
key.dimension != xenos::DataDimension::k2DOrStacked) {
|
|
return nullptr;
|
|
}
|
|
VulkanTexture* texture =
|
|
static_cast<VulkanTexture*>(FindOrCreateTexture(key));
|
|
if (!texture) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
VkImageView texture_view = texture->GetView(
|
|
false, GuestToHostSwizzle(fetch.swizzle, GetHostFormatSwizzle(key)),
|
|
false);
|
|
if (texture_view == VK_NULL_HANDLE) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
if (!LoadTextureData(*texture)) {
|
|
XELOGE("Failed to load texture data for swap texture");
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
texture->MarkAsUsed();
|
|
VulkanTexture::Usage old_usage =
|
|
texture->SetUsage(VulkanTexture::Usage::kSwapSampled);
|
|
if (old_usage != VulkanTexture::Usage::kSwapSampled) {
|
|
VkPipelineStageFlags src_stage_mask, dst_stage_mask;
|
|
VkAccessFlags src_access_mask, dst_access_mask;
|
|
VkImageLayout old_layout, new_layout;
|
|
GetTextureUsageMasks(old_usage, src_stage_mask, src_access_mask,
|
|
old_layout);
|
|
GetTextureUsageMasks(VulkanTexture::Usage::kSwapSampled, dst_stage_mask,
|
|
dst_access_mask, new_layout);
|
|
command_processor_.PushImageMemoryBarrier(
|
|
texture->image(), ui::vulkan::util::InitializeSubresourceRange(),
|
|
src_stage_mask, dst_stage_mask, src_access_mask, dst_access_mask,
|
|
old_layout, new_layout);
|
|
}
|
|
// Only texture->key, not the result of BindingInfoFromFetchConstant, contains
|
|
// whether the texture is scaled.
|
|
key = texture->key();
|
|
width_scaled_out =
|
|
key.GetWidth() * (key.scaled_resolve ? draw_resolution_scale_x() : 1);
|
|
height_scaled_out =
|
|
key.GetHeight() * (key.scaled_resolve ? draw_resolution_scale_y() : 1);
|
|
format_out = key.format;
|
|
return texture_view;
|
|
}
|
|
|
|
bool VulkanTextureCache::IsScaledResolveSupportedForFormat(
|
|
TextureKey key) const {
|
|
// Check if the format has a valid host format pair, meaning we can handle it
|
|
const HostFormatPair& host_format_pair = GetHostFormatPair(key);
|
|
return host_format_pair.format_unsigned.format != VK_FORMAT_UNDEFINED;
|
|
}
|
|
|
|
bool VulkanTextureCache::IsSignedVersionSeparateForFormat(
|
|
TextureKey key) const {
|
|
const HostFormatPair& host_format_pair = GetHostFormatPair(key);
|
|
if (host_format_pair.format_unsigned.format == VK_FORMAT_UNDEFINED ||
|
|
host_format_pair.format_signed.format == VK_FORMAT_UNDEFINED) {
|
|
// Just one signedness.
|
|
return false;
|
|
}
|
|
return !host_format_pair.unsigned_signed_compatible;
|
|
}
|
|
|
|
uint32_t VulkanTextureCache::GetHostFormatSwizzle(TextureKey key) const {
|
|
return GetHostFormatPair(key).swizzle;
|
|
}
|
|
|
|
uint32_t VulkanTextureCache::GetMaxHostTextureWidthHeight(
|
|
xenos::DataDimension dimension) const {
|
|
const ui::vulkan::VulkanDevice::Properties& device_properties =
|
|
command_processor_.GetVulkanDevice()->properties();
|
|
switch (dimension) {
|
|
case xenos::DataDimension::k1D:
|
|
case xenos::DataDimension::k2DOrStacked:
|
|
// 1D and 2D are emulated as 2D arrays.
|
|
return device_properties.maxImageDimension2D;
|
|
case xenos::DataDimension::k3D:
|
|
return device_properties.maxImageDimension3D;
|
|
case xenos::DataDimension::kCube:
|
|
return device_properties.maxImageDimensionCube;
|
|
default:
|
|
assert_unhandled_case(dimension);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint32_t VulkanTextureCache::GetMaxHostTextureDepthOrArraySize(
|
|
xenos::DataDimension dimension) const {
|
|
const ui::vulkan::VulkanDevice::Properties& device_properties =
|
|
command_processor_.GetVulkanDevice()->properties();
|
|
switch (dimension) {
|
|
case xenos::DataDimension::k1D:
|
|
case xenos::DataDimension::k2DOrStacked:
|
|
// 1D and 2D are emulated as 2D arrays.
|
|
return device_properties.maxImageArrayLayers;
|
|
case xenos::DataDimension::k3D:
|
|
return device_properties.maxImageDimension3D;
|
|
case xenos::DataDimension::kCube:
|
|
// Not requesting the imageCubeArray feature, and the Xenos doesn't
|
|
// support cube map arrays.
|
|
return 6;
|
|
default:
|
|
assert_unhandled_case(dimension);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
|
|
TextureKey key) {
|
|
VkFormat formats[] = {VK_FORMAT_UNDEFINED, VK_FORMAT_UNDEFINED};
|
|
const HostFormatPair& host_format = GetHostFormatPair(key);
|
|
if (host_format.format_signed.format == VK_FORMAT_UNDEFINED) {
|
|
// Only the unsigned format may be available, if at all.
|
|
formats[0] = host_format.format_unsigned.format;
|
|
} else if (host_format.format_unsigned.format == VK_FORMAT_UNDEFINED) {
|
|
// Only the signed format may be available, if at all.
|
|
formats[0] = host_format.format_signed.format;
|
|
} else {
|
|
// Both unsigned and signed formats are available.
|
|
if (IsSignedVersionSeparateForFormat(key)) {
|
|
formats[0] = key.signed_separate ? host_format.format_signed.format
|
|
: host_format.format_unsigned.format;
|
|
} else {
|
|
// Same format for unsigned and signed, or compatible formats.
|
|
formats[0] = host_format.format_unsigned.format;
|
|
if (host_format.format_signed.format !=
|
|
host_format.format_unsigned.format) {
|
|
assert_not_zero(host_format.unsigned_signed_compatible);
|
|
formats[1] = host_format.format_signed.format;
|
|
}
|
|
}
|
|
}
|
|
if (formats[0] == VK_FORMAT_UNDEFINED) {
|
|
// TODO(Triang3l): If there's no best format, set that a format unsupported
|
|
// by the emulator completely is used to report at the end of the frame.
|
|
return nullptr;
|
|
}
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
command_processor_.GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
|
|
bool is_3d = key.dimension == xenos::DataDimension::k3D;
|
|
uint32_t depth_or_array_size = key.GetDepthOrArraySize();
|
|
|
|
VkImageCreateInfo image_create_info;
|
|
VkImageCreateInfo* image_create_info_last = &image_create_info;
|
|
image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
image_create_info.pNext = nullptr;
|
|
image_create_info.flags = 0;
|
|
if (formats[1] != VK_FORMAT_UNDEFINED) {
|
|
image_create_info.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
|
}
|
|
if (key.dimension == xenos::DataDimension::kCube) {
|
|
image_create_info.flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
}
|
|
image_create_info.imageType = is_3d ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D;
|
|
image_create_info.format = formats[0];
|
|
image_create_info.extent.width = key.GetWidth();
|
|
image_create_info.extent.height = key.GetHeight();
|
|
if (key.scaled_resolve) {
|
|
image_create_info.extent.width *= draw_resolution_scale_x();
|
|
image_create_info.extent.height *= draw_resolution_scale_y();
|
|
}
|
|
image_create_info.extent.depth = is_3d ? depth_or_array_size : 1;
|
|
image_create_info.mipLevels = key.mip_max_level + 1;
|
|
image_create_info.arrayLayers = is_3d ? 1 : depth_or_array_size;
|
|
image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
image_create_info.usage =
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
image_create_info.queueFamilyIndexCount = 0;
|
|
image_create_info.pQueueFamilyIndices = nullptr;
|
|
image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
VkImageFormatListCreateInfo image_format_list_create_info;
|
|
if (formats[1] != VK_FORMAT_UNDEFINED &&
|
|
vulkan_device->extensions().ext_1_2_KHR_image_format_list) {
|
|
image_create_info_last->pNext = &image_format_list_create_info;
|
|
image_create_info_last =
|
|
reinterpret_cast<VkImageCreateInfo*>(&image_format_list_create_info);
|
|
image_format_list_create_info.sType =
|
|
VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO;
|
|
image_format_list_create_info.pNext = nullptr;
|
|
image_format_list_create_info.viewFormatCount = 2;
|
|
image_format_list_create_info.pViewFormats = formats;
|
|
}
|
|
|
|
VmaAllocationCreateInfo allocation_create_info = {};
|
|
allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
|
|
|
VkImage image;
|
|
VmaAllocation allocation;
|
|
if (vmaCreateImage(vma_allocator_, &image_create_info,
|
|
&allocation_create_info, &image, &allocation, nullptr)) {
|
|
return nullptr;
|
|
}
|
|
|
|
return std::unique_ptr<Texture>(
|
|
new VulkanTexture(*this, key, image, allocation));
|
|
}
|
|
|
|
bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
|
|
bool load_base,
|
|
bool load_mips) {
|
|
VulkanTexture& vulkan_texture = static_cast<VulkanTexture&>(texture);
|
|
TextureKey texture_key = vulkan_texture.key();
|
|
|
|
// Get the pipeline.
|
|
const HostFormatPair& host_format_pair = GetHostFormatPair(texture_key);
|
|
bool host_format_is_signed;
|
|
if (IsSignedVersionSeparateForFormat(texture_key)) {
|
|
host_format_is_signed = bool(texture_key.signed_separate);
|
|
} else {
|
|
host_format_is_signed =
|
|
host_format_pair.format_unsigned.load_shader == kLoadShaderIndexUnknown;
|
|
}
|
|
const HostFormat& host_format = host_format_is_signed
|
|
? host_format_pair.format_signed
|
|
: host_format_pair.format_unsigned;
|
|
LoadShaderIndex load_shader = host_format.load_shader;
|
|
if (load_shader == kLoadShaderIndexUnknown) {
|
|
return false;
|
|
}
|
|
VkPipeline pipeline = texture_key.scaled_resolve
|
|
? load_pipelines_scaled_[load_shader]
|
|
: load_pipelines_[load_shader];
|
|
if (pipeline == VK_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
const LoadShaderInfo& load_shader_info = GetLoadShaderInfo(load_shader);
|
|
|
|
// Get the guest layout.
|
|
const texture_util::TextureGuestLayout& guest_layout =
|
|
vulkan_texture.guest_layout();
|
|
xenos::DataDimension dimension = texture_key.dimension;
|
|
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<VulkanSharedMemory&>(shared_memory());
|
|
std::array<VkWriteDescriptorSet, 3> write_descriptor_sets;
|
|
uint32_t write_descriptor_set_count = 0;
|
|
VkDescriptorSet descriptor_set_dest =
|
|
command_processor_.AllocateSingleTransientDescriptor(
|
|
VulkanCommandProcessor::SingleTransientDescriptorLayout ::
|
|
kStorageBufferCompute);
|
|
if (!descriptor_set_dest) {
|
|
return false;
|
|
}
|
|
VkDescriptorBufferInfo write_descriptor_set_dest_buffer_info;
|
|
{
|
|
write_descriptor_set_dest_buffer_info.buffer = scratch_buffer;
|
|
write_descriptor_set_dest_buffer_info.offset = 0;
|
|
write_descriptor_set_dest_buffer_info.range = host_buffer_size;
|
|
VkWriteDescriptorSet& write_descriptor_set_dest =
|
|
write_descriptor_sets[write_descriptor_set_count++];
|
|
write_descriptor_set_dest.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
write_descriptor_set_dest.pNext = nullptr;
|
|
write_descriptor_set_dest.dstSet = descriptor_set_dest;
|
|
write_descriptor_set_dest.dstBinding = 0;
|
|
write_descriptor_set_dest.dstArrayElement = 0;
|
|
write_descriptor_set_dest.descriptorCount = 1;
|
|
write_descriptor_set_dest.descriptorType =
|
|
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
write_descriptor_set_dest.pImageInfo = nullptr;
|
|
write_descriptor_set_dest.pBufferInfo =
|
|
&write_descriptor_set_dest_buffer_info;
|
|
write_descriptor_set_dest.pTexelBufferView = nullptr;
|
|
}
|
|
// TODO(Triang3l): Use a single 512 MB shared memory binding if possible.
|
|
// 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<VulkanTexture*>(binding->texture)
|
|
->GetView(false, binding->host_swizzle);
|
|
}
|
|
if (binding->texture_signed &&
|
|
texture_util::IsAnySignSigned(binding->swizzled_signs)) {
|
|
vulkan_binding.image_view_signed =
|
|
static_cast<VulkanTexture*>(binding->texture_signed)
|
|
->GetView(true, binding->host_swizzle);
|
|
}
|
|
} else {
|
|
VulkanTexture* texture = static_cast<VulkanTexture*>(binding->texture);
|
|
if (texture) {
|
|
if (texture_util::IsAnySignNotSigned(binding->swizzled_signs)) {
|
|
vulkan_binding.image_view_unsigned =
|
|
texture->GetView(false, binding->host_swizzle);
|
|
}
|
|
if (texture_util::IsAnySignSigned(binding->swizzled_signs)) {
|
|
vulkan_binding.image_view_signed =
|
|
texture->GetView(true, binding->host_swizzle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
VulkanTextureCache::VulkanTexture::VulkanTexture(
|
|
VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
|
|
VmaAllocation allocation)
|
|
: 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<const VulkanTextureCache&>(texture_cache());
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
vulkan_texture_cache.command_processor_.GetVulkanDevice();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
for (const auto& view_pair : views_) {
|
|
dfn.vkDestroyImageView(device, view_pair.second, nullptr);
|
|
}
|
|
vmaDestroyImage(vulkan_texture_cache.vma_allocator_, image_, allocation_);
|
|
}
|
|
|
|
VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
|
|
uint32_t host_swizzle,
|
|
bool is_array) {
|
|
xenos::DataDimension dimension = key().dimension;
|
|
if (dimension == xenos::DataDimension::k3D ||
|
|
dimension == xenos::DataDimension::kCube) {
|
|
is_array = false;
|
|
}
|
|
|
|
const VulkanTextureCache& vulkan_texture_cache =
|
|
static_cast<const VulkanTextureCache&>(texture_cache());
|
|
|
|
ViewKey view_key;
|
|
|
|
const HostFormatPair& host_format_pair =
|
|
vulkan_texture_cache.GetHostFormatPair(key());
|
|
VkFormat format = (is_signed ? host_format_pair.format_signed
|
|
: host_format_pair.format_unsigned)
|
|
.format;
|
|
if (format == VK_FORMAT_UNDEFINED) {
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
// If not distinguishing between unsigned and signed formats for the same
|
|
// image, don't create two views. As this happens within an image, no need to
|
|
// care about whether unsigned and signed images are separate - if they are
|
|
// (or if there are only unsigned or only signed images), this image will have
|
|
// either all views unsigned or all views signed.
|
|
view_key.is_signed_separate_view =
|
|
is_signed && (host_format_pair.format_signed.format !=
|
|
host_format_pair.format_unsigned.format);
|
|
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
vulkan_texture_cache.command_processor_.GetVulkanDevice();
|
|
|
|
if (!vulkan_device->properties().imageViewFormatSwizzle) {
|
|
host_swizzle = xenos::XE_GPU_TEXTURE_SWIZZLE_RGBA;
|
|
}
|
|
view_key.host_swizzle = host_swizzle;
|
|
|
|
view_key.is_array = uint32_t(is_array);
|
|
|
|
// Try to find an existing view.
|
|
auto it = views_.find(view_key);
|
|
if (it != views_.end()) {
|
|
return it->second;
|
|
}
|
|
|
|
// Create a new view.
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
VkImageViewCreateInfo view_create_info;
|
|
view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
view_create_info.pNext = nullptr;
|
|
view_create_info.flags = 0;
|
|
view_create_info.image = image();
|
|
view_create_info.format = format;
|
|
view_create_info.components.r = GetComponentSwizzle(host_swizzle, 0);
|
|
view_create_info.components.g = GetComponentSwizzle(host_swizzle, 1);
|
|
view_create_info.components.b = GetComponentSwizzle(host_swizzle, 2);
|
|
view_create_info.components.a = GetComponentSwizzle(host_swizzle, 3);
|
|
view_create_info.subresourceRange =
|
|
ui::vulkan::util::InitializeSubresourceRange();
|
|
switch (dimension) {
|
|
case xenos::DataDimension::k3D:
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
|
|
break;
|
|
case xenos::DataDimension::kCube:
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
|
break;
|
|
default:
|
|
if (is_array) {
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
} else {
|
|
view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
view_create_info.subresourceRange.layerCount = 1;
|
|
}
|
|
break;
|
|
}
|
|
VkImageView view;
|
|
if (dfn.vkCreateImageView(device, &view_create_info, nullptr, &view) !=
|
|
VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to create an image view for Vulkan format "
|
|
"{} ({}signed) with swizzle 0x{:3X}",
|
|
uint32_t(format), is_signed ? "" : "un", host_swizzle);
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
views_.emplace(view_key, view);
|
|
return view;
|
|
}
|
|
|
|
VulkanTextureCache::VulkanTextureCache(
|
|
const RegisterFile& register_file, VulkanSharedMemory& shared_memory,
|
|
uint32_t draw_resolution_scale_x, uint32_t draw_resolution_scale_y,
|
|
VulkanCommandProcessor& command_processor,
|
|
VkPipelineStageFlags guest_shader_pipeline_stages)
|
|
: TextureCache(register_file, shared_memory, draw_resolution_scale_x,
|
|
draw_resolution_scale_y),
|
|
command_processor_(command_processor),
|
|
guest_shader_pipeline_stages_(guest_shader_pipeline_stages) {}
|
|
|
|
bool VulkanTextureCache::Initialize() {
|
|
const ui::vulkan::VulkanDevice* const vulkan_device =
|
|
command_processor_.GetVulkanDevice();
|
|
const ui::vulkan::VulkanInstance::Functions& ifn =
|
|
vulkan_device->vulkan_instance()->functions();
|
|
const VkPhysicalDevice physical_device = vulkan_device->physical_device();
|
|
const ui::vulkan::VulkanDevice::Functions& dfn = vulkan_device->functions();
|
|
const VkDevice device = vulkan_device->device();
|
|
const ui::vulkan::VulkanDevice::Properties& device_properties =
|
|
vulkan_device->properties();
|
|
|
|
// Vulkan Memory Allocator.
|
|
|
|
vma_allocator_ = ui::vulkan::CreateVmaAllocator(vulkan_device, true);
|
|
if (vma_allocator_ == VK_NULL_HANDLE) {
|
|
return false;
|
|
}
|
|
|
|
// Image formats.
|
|
|
|
// Initialize to the best formats.
|
|
for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
|
|
host_formats_[i] = kBestHostFormats[i];
|
|
}
|
|
|
|
// Check format support and switch to fallbacks if needed.
|
|
constexpr VkFormatFeatureFlags kLinearFilterFeatures =
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT |
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT;
|
|
VkFormatProperties r16_unorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(physical_device, VK_FORMAT_R16_UNORM,
|
|
&r16_unorm_properties);
|
|
VkFormatProperties r16_snorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(physical_device, VK_FORMAT_R16_SNORM,
|
|
&r16_snorm_properties);
|
|
VkFormatProperties r16g16_unorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_R16G16_UNORM, &r16g16_unorm_properties);
|
|
VkFormatProperties r16g16_snorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_R16G16_SNORM, &r16g16_snorm_properties);
|
|
VkFormatProperties r16g16b16a16_unorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
|
|
VK_FORMAT_R16G16B16A16_UNORM,
|
|
&r16g16b16a16_unorm_properties);
|
|
VkFormatProperties r16g16b16a16_snorm_properties;
|
|
ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
|
|
VK_FORMAT_R16G16B16A16_SNORM,
|
|
&r16g16b16a16_snorm_properties);
|
|
VkFormatProperties format_properties;
|
|
// TODO(Triang3l): k_2_10_10_10 signed -> filterable R16G16B16A16_SFLOAT
|
|
// (enough storage precision, possibly unwanted filtering precision change).
|
|
// k_Cr_Y1_Cb_Y0_REP, k_Y1_Cr_Y0_Cb_REP.
|
|
HostFormatPair& host_format_gbgr =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_Cr_Y1_Cb_Y0_REP)];
|
|
assert_true(host_format_gbgr.format_unsigned.format ==
|
|
VK_FORMAT_G8B8G8R8_422_UNORM_KHR);
|
|
assert_true(host_format_gbgr.format_signed.format ==
|
|
VK_FORMAT_R8G8B8A8_SNORM);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_G8B8G8R8_422_UNORM_KHR, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_gbgr.format_unsigned.load_shader = kLoadShaderIndexGBGR8ToRGB8;
|
|
host_format_gbgr.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
host_format_gbgr.format_unsigned.block_compressed = false;
|
|
host_format_gbgr.unsigned_signed_compatible = true;
|
|
}
|
|
HostFormatPair& host_format_bgrg =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_Y1_Cr_Y0_Cb_REP)];
|
|
assert_true(host_format_bgrg.format_unsigned.format ==
|
|
VK_FORMAT_B8G8R8G8_422_UNORM_KHR);
|
|
assert_true(host_format_bgrg.format_signed.format ==
|
|
VK_FORMAT_R8G8B8A8_SNORM);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_B8G8R8G8_422_UNORM_KHR, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_bgrg.format_unsigned.load_shader = kLoadShaderIndexBGRG8ToRGB8;
|
|
host_format_bgrg.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
host_format_bgrg.format_unsigned.block_compressed = false;
|
|
host_format_bgrg.unsigned_signed_compatible = true;
|
|
}
|
|
// TODO(Triang3l): k_10_11_11 -> filterable R16G16B16A16_SFLOAT (enough
|
|
// storage precision, possibly unwanted filtering precision change).
|
|
// TODO(Triang3l): k_11_11_10 -> filterable R16G16B16A16_SFLOAT (enough
|
|
// storage precision, possibly unwanted filtering precision change).
|
|
// S3TC.
|
|
// Not checking the textureCompressionBC feature because its availability
|
|
// means that all BC formats are supported, however, the device may expose
|
|
// some BC formats without this feature. Xenia doesn't use BC6H and BC7 at
|
|
// all, and has fallbacks for each used format.
|
|
// TODO(Triang3l): Raise the host texture memory usage limit if S3TC has to be
|
|
// decompressed.
|
|
// TODO(Triang3l): S3TC -> 5551 or 4444 as an option.
|
|
// TODO(Triang3l): S3TC -> ETC2 / EAC (a huge research topic).
|
|
HostFormatPair& host_format_dxt1 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT1)];
|
|
assert_true(host_format_dxt1.format_unsigned.format ==
|
|
VK_FORMAT_BC1_RGBA_UNORM_BLOCK);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_dxt1.format_unsigned.load_shader = kLoadShaderIndexDXT1ToRGBA8;
|
|
host_format_dxt1.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
host_format_dxt1.format_unsigned.block_compressed = false;
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT1_AS_16_16_16_16)] =
|
|
host_format_dxt1;
|
|
}
|
|
HostFormatPair& host_format_dxt2_3 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT2_3)];
|
|
assert_true(host_format_dxt2_3.format_unsigned.format ==
|
|
VK_FORMAT_BC2_UNORM_BLOCK);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_BC2_UNORM_BLOCK, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_dxt2_3.format_unsigned.load_shader =
|
|
kLoadShaderIndexDXT3ToRGBA8;
|
|
host_format_dxt2_3.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
host_format_dxt2_3.format_unsigned.block_compressed = false;
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT2_3_AS_16_16_16_16)] =
|
|
host_format_dxt2_3;
|
|
}
|
|
HostFormatPair& host_format_dxt4_5 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT4_5)];
|
|
assert_true(host_format_dxt4_5.format_unsigned.format ==
|
|
VK_FORMAT_BC3_UNORM_BLOCK);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_BC3_UNORM_BLOCK, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_dxt4_5.format_unsigned.load_shader =
|
|
kLoadShaderIndexDXT5ToRGBA8;
|
|
host_format_dxt4_5.format_unsigned.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
host_format_dxt4_5.format_unsigned.block_compressed = false;
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT4_5_AS_16_16_16_16)] =
|
|
host_format_dxt4_5;
|
|
}
|
|
HostFormatPair& host_format_dxn =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXN)];
|
|
assert_true(host_format_dxn.format_unsigned.format ==
|
|
VK_FORMAT_BC5_UNORM_BLOCK);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_BC5_UNORM_BLOCK, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_dxn.format_unsigned.load_shader = kLoadShaderIndexDXNToRG8;
|
|
host_format_dxn.format_unsigned.format = VK_FORMAT_R8G8_UNORM;
|
|
host_format_dxn.format_unsigned.block_compressed = false;
|
|
}
|
|
HostFormatPair& host_format_dxt5a =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_DXT5A)];
|
|
assert_true(host_format_dxt5a.format_unsigned.format ==
|
|
VK_FORMAT_BC4_UNORM_BLOCK);
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, VK_FORMAT_BC4_UNORM_BLOCK, &format_properties);
|
|
if ((format_properties.optimalTilingFeatures & kLinearFilterFeatures) !=
|
|
kLinearFilterFeatures) {
|
|
host_format_dxt5a.format_unsigned.load_shader = kLoadShaderIndexDXT5AToR8;
|
|
host_format_dxt5a.format_unsigned.format = VK_FORMAT_R8_UNORM;
|
|
host_format_dxt5a.format_unsigned.block_compressed = false;
|
|
}
|
|
// k_16, k_16_16, k_16_16_16_16 - UNORM / SNORM are optional, fall back to
|
|
// SFLOAT, which is mandatory and is always filterable (the guest 16-bit
|
|
// format is filterable, 16-bit fixed-point is the full texture filtering
|
|
// precision on the Xenos overall). Let the user choose what's more important,
|
|
// precision (use host UNORM / SNORM if available even if they're not
|
|
// filterable) or filterability (use host UNORM / SNORM only if they're
|
|
// available and filterable).
|
|
// TODO(Triang3l): Expose a cvar for selecting the preference (filterability
|
|
// or precision).
|
|
VkFormatFeatureFlags norm16_required_features =
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
|
HostFormatPair& host_format_16 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_16)];
|
|
assert_true(host_format_16.format_unsigned.format == VK_FORMAT_R16_UNORM);
|
|
if ((r16_unorm_properties.optimalTilingFeatures & norm16_required_features) !=
|
|
norm16_required_features) {
|
|
host_format_16.format_unsigned.load_shader =
|
|
kLoadShaderIndexR16UNormToFloat;
|
|
host_format_16.format_unsigned.format = VK_FORMAT_R16_SFLOAT;
|
|
}
|
|
assert_true(host_format_16.format_signed.format == VK_FORMAT_R16_SNORM);
|
|
if ((r16_snorm_properties.optimalTilingFeatures & norm16_required_features) !=
|
|
norm16_required_features) {
|
|
host_format_16.format_signed.load_shader = kLoadShaderIndexR16SNormToFloat;
|
|
host_format_16.format_signed.format = VK_FORMAT_R16_SFLOAT;
|
|
}
|
|
host_format_16.unsigned_signed_compatible =
|
|
(host_format_16.format_unsigned.format == VK_FORMAT_R16_UNORM &&
|
|
host_format_16.format_signed.format == VK_FORMAT_R16_SNORM) ||
|
|
(host_format_16.format_unsigned.format == VK_FORMAT_R16_SFLOAT &&
|
|
host_format_16.format_signed.format == VK_FORMAT_R16_SFLOAT);
|
|
HostFormatPair& host_format_16_16 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_16_16)];
|
|
assert_true(host_format_16_16.format_unsigned.format ==
|
|
VK_FORMAT_R16G16_UNORM);
|
|
if ((r16g16_unorm_properties.optimalTilingFeatures &
|
|
norm16_required_features) != norm16_required_features) {
|
|
host_format_16_16.format_unsigned.load_shader =
|
|
kLoadShaderIndexRG16UNormToFloat;
|
|
host_format_16_16.format_unsigned.format = VK_FORMAT_R16G16_SFLOAT;
|
|
}
|
|
assert_true(host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SNORM);
|
|
if ((r16g16_snorm_properties.optimalTilingFeatures &
|
|
norm16_required_features) != norm16_required_features) {
|
|
host_format_16_16.format_signed.load_shader =
|
|
kLoadShaderIndexRG16SNormToFloat;
|
|
host_format_16_16.format_signed.format = VK_FORMAT_R16G16_SFLOAT;
|
|
}
|
|
host_format_16_16.unsigned_signed_compatible =
|
|
(host_format_16_16.format_unsigned.format == VK_FORMAT_R16G16_UNORM &&
|
|
host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SNORM) ||
|
|
(host_format_16_16.format_unsigned.format == VK_FORMAT_R16G16_SFLOAT &&
|
|
host_format_16_16.format_signed.format == VK_FORMAT_R16G16_SFLOAT);
|
|
HostFormatPair& host_format_16_16_16_16 =
|
|
host_formats_[uint32_t(xenos::TextureFormat::k_16_16_16_16)];
|
|
assert_true(host_format_16_16_16_16.format_unsigned.format ==
|
|
VK_FORMAT_R16G16B16A16_UNORM);
|
|
if ((r16g16b16a16_unorm_properties.optimalTilingFeatures &
|
|
norm16_required_features) != norm16_required_features) {
|
|
host_format_16_16_16_16.format_unsigned.load_shader =
|
|
kLoadShaderIndexRGBA16UNormToFloat;
|
|
host_format_16_16_16_16.format_unsigned.format =
|
|
VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
}
|
|
assert_true(host_format_16_16_16_16.format_signed.format ==
|
|
VK_FORMAT_R16G16B16A16_SNORM);
|
|
if ((r16g16b16a16_snorm_properties.optimalTilingFeatures &
|
|
norm16_required_features) != norm16_required_features) {
|
|
host_format_16_16_16_16.format_signed.load_shader =
|
|
kLoadShaderIndexRGBA16SNormToFloat;
|
|
host_format_16_16_16_16.format_signed.format =
|
|
VK_FORMAT_R16G16B16A16_SFLOAT;
|
|
}
|
|
host_format_16_16_16_16.unsigned_signed_compatible =
|
|
(host_format_16_16_16_16.format_unsigned.format ==
|
|
VK_FORMAT_R16G16B16A16_UNORM &&
|
|
host_format_16_16_16_16.format_signed.format ==
|
|
VK_FORMAT_R16G16B16A16_SNORM) ||
|
|
(host_format_16_16_16_16.format_unsigned.format ==
|
|
VK_FORMAT_R16G16B16A16_SFLOAT &&
|
|
host_format_16_16_16_16.format_signed.format ==
|
|
VK_FORMAT_R16G16B16A16_SFLOAT);
|
|
|
|
// Normalize format information structures.
|
|
for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
|
|
HostFormatPair& host_format = host_formats_[i];
|
|
// load_shader_index is left uninitialized for the tail (non-existent
|
|
// formats), kLoadShaderIndexUnknown may be non-zero, and format support may
|
|
// be disabled by setting the format to VK_FORMAT_UNDEFINED.
|
|
if (host_format.format_unsigned.format == VK_FORMAT_UNDEFINED) {
|
|
host_format.format_unsigned.load_shader = kLoadShaderIndexUnknown;
|
|
}
|
|
assert_false(host_format.format_unsigned.load_shader ==
|
|
kLoadShaderIndexUnknown &&
|
|
host_format.format_unsigned.format != VK_FORMAT_UNDEFINED);
|
|
if (host_format.format_unsigned.load_shader == kLoadShaderIndexUnknown) {
|
|
host_format.format_unsigned.format = VK_FORMAT_UNDEFINED;
|
|
// Surely known it's unsupported with these two conditions.
|
|
host_format.format_unsigned.linear_filterable = false;
|
|
}
|
|
if (host_format.format_signed.format == VK_FORMAT_UNDEFINED) {
|
|
host_format.format_signed.load_shader = kLoadShaderIndexUnknown;
|
|
}
|
|
assert_false(host_format.format_signed.load_shader ==
|
|
kLoadShaderIndexUnknown &&
|
|
host_format.format_signed.format != VK_FORMAT_UNDEFINED);
|
|
if (host_format.format_signed.load_shader == kLoadShaderIndexUnknown) {
|
|
host_format.format_signed.format = VK_FORMAT_UNDEFINED;
|
|
// Surely known it's unsupported with these two conditions.
|
|
host_format.format_signed.linear_filterable = false;
|
|
}
|
|
|
|
// Check if the formats are supported and are linear-filterable.
|
|
if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
|
|
ifn.vkGetPhysicalDeviceFormatProperties(
|
|
physical_device, host_format.format_unsigned.format,
|
|
&format_properties);
|
|
if (format_properties.optimalTilingFeatures &
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
|
|
host_format.format_unsigned.linear_filterable =
|
|
(format_properties.optimalTilingFeatures &
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
|
} else {
|
|
host_format.format_unsigned.format = VK_FORMAT_UNDEFINED;
|
|
host_format.format_unsigned.load_shader = kLoadShaderIndexUnknown;
|
|
host_format.format_unsigned.linear_filterable = false;
|
|
}
|
|
}
|
|
if (host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
|
|
ifn.vkGetPhysicalDeviceFormatProperties(physical_device,
|
|
host_format.format_signed.format,
|
|
&format_properties);
|
|
if (format_properties.optimalTilingFeatures &
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT) {
|
|
host_format.format_signed.linear_filterable =
|
|
(format_properties.optimalTilingFeatures &
|
|
VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT) != 0;
|
|
} else {
|
|
host_format.format_signed.format = VK_FORMAT_UNDEFINED;
|
|
host_format.format_signed.load_shader = kLoadShaderIndexUnknown;
|
|
host_format.format_signed.linear_filterable = false;
|
|
}
|
|
}
|
|
|
|
// Log which formats are not supported or supported via fallbacks.
|
|
const HostFormatPair& best_host_format = kBestHostFormats[i];
|
|
const char* guest_format_name =
|
|
FormatInfo::GetName(xenos::TextureFormat(i));
|
|
if (best_host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
|
|
assert_not_null(guest_format_name);
|
|
if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED) {
|
|
if (host_format.format_unsigned.format !=
|
|
best_host_format.format_unsigned.format) {
|
|
XELOGGPU(
|
|
"VulkanTextureCache: Format {} (unsigned) is supported via a "
|
|
"fallback format (using the Vulkan format {} instead of the "
|
|
"preferred {})",
|
|
guest_format_name, uint32_t(host_format.format_unsigned.format),
|
|
uint32_t(best_host_format.format_unsigned.format));
|
|
}
|
|
} else {
|
|
XELOGGPU(
|
|
"VulkanTextureCache: Format {} (unsigned) is not supported by the "
|
|
"device (preferred Vulkan format is {})",
|
|
guest_format_name,
|
|
uint32_t(best_host_format.format_unsigned.format));
|
|
}
|
|
}
|
|
if (best_host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
|
|
assert_not_null(guest_format_name);
|
|
if (host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
|
|
if (host_format.format_signed.format !=
|
|
best_host_format.format_signed.format) {
|
|
XELOGGPU(
|
|
"VulkanTextureCache: Format {} (signed) is supported via a "
|
|
"fallback format (using the Vulkan format {} instead of the "
|
|
"preferred {})",
|
|
guest_format_name, uint32_t(host_format.format_signed.format),
|
|
uint32_t(best_host_format.format_signed.format));
|
|
}
|
|
} else {
|
|
XELOGGPU(
|
|
"VulkanTextureCache: Format {} (signed) is not supported by the "
|
|
"device (preferred Vulkan format is {})",
|
|
guest_format_name, uint32_t(best_host_format.format_signed.format));
|
|
}
|
|
}
|
|
|
|
// Signednesses with different load shaders must have the data loaded
|
|
// differently, therefore can't share the image even if the format is the
|
|
// same. Also, if there's only one version, simplify the logic - there can't
|
|
// be compatibility between two formats when one of them is undefined.
|
|
if (host_format.format_unsigned.format != VK_FORMAT_UNDEFINED &&
|
|
host_format.format_signed.format != VK_FORMAT_UNDEFINED) {
|
|
if (host_format.format_unsigned.load_shader ==
|
|
host_format.format_signed.load_shader) {
|
|
if (host_format.format_unsigned.format ==
|
|
host_format.format_signed.format) {
|
|
// Same format after all the fallbacks - force compatibilty.
|
|
host_format.unsigned_signed_compatible = true;
|
|
}
|
|
} else {
|
|
host_format.unsigned_signed_compatible = false;
|
|
}
|
|
// Formats within the same compatibility class must have the same block
|
|
// size, though the fallbacks are configured incorrectly if that's not the
|
|
// case (since such formats just can't be in one compatibility class).
|
|
assert_false(host_format.unsigned_signed_compatible &&
|
|
host_format.format_unsigned.block_compressed !=
|
|
host_format.format_signed.block_compressed);
|
|
if (host_format.unsigned_signed_compatible &&
|
|
host_format.format_unsigned.block_compressed !=
|
|
host_format.format_signed.block_compressed) {
|
|
host_format.unsigned_signed_compatible = false;
|
|
}
|
|
} else {
|
|
host_format.unsigned_signed_compatible = false;
|
|
}
|
|
}
|
|
|
|
// Load pipeline layout.
|
|
|
|
VkDescriptorSetLayout load_descriptor_set_layouts[kLoadDescriptorSetCount] =
|
|
{};
|
|
VkDescriptorSetLayout load_descriptor_set_layout_storage_buffer =
|
|
command_processor_.GetSingleTransientDescriptorLayout(
|
|
VulkanCommandProcessor::SingleTransientDescriptorLayout ::
|
|
kStorageBufferCompute);
|
|
assert_true(load_descriptor_set_layout_storage_buffer != VK_NULL_HANDLE);
|
|
load_descriptor_set_layouts[kLoadDescriptorSetIndexDestination] =
|
|
load_descriptor_set_layout_storage_buffer;
|
|
load_descriptor_set_layouts[kLoadDescriptorSetIndexSource] =
|
|
load_descriptor_set_layout_storage_buffer;
|
|
VkPushConstantRange load_pipeline_layout_push_constant_range;
|
|
load_pipeline_layout_push_constant_range.stageFlags =
|
|
VK_SHADER_STAGE_COMPUTE_BIT;
|
|
load_pipeline_layout_push_constant_range.offset = 0;
|
|
load_pipeline_layout_push_constant_range.size = sizeof(LoadConstants);
|
|
VkPipelineLayoutCreateInfo load_pipeline_layout_create_info;
|
|
load_pipeline_layout_create_info.sType =
|
|
VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
load_pipeline_layout_create_info.pNext = nullptr;
|
|
load_pipeline_layout_create_info.flags = 0;
|
|
load_pipeline_layout_create_info.setLayoutCount = kLoadDescriptorSetCount;
|
|
load_pipeline_layout_create_info.pSetLayouts = load_descriptor_set_layouts;
|
|
load_pipeline_layout_create_info.pushConstantRangeCount = 1;
|
|
load_pipeline_layout_create_info.pPushConstantRanges =
|
|
&load_pipeline_layout_push_constant_range;
|
|
if (dfn.vkCreatePipelineLayout(device, &load_pipeline_layout_create_info,
|
|
nullptr, &load_pipeline_layout_)) {
|
|
XELOGE("VulkanTexture: Failed to create the texture load pipeline layout");
|
|
return false;
|
|
}
|
|
|
|
// Load pipelines, only the ones needed for the formats that will be used.
|
|
|
|
bool load_shaders_needed[kLoadShaderCount] = {};
|
|
for (size_t i = 0; i < xe::countof(host_formats_); ++i) {
|
|
const HostFormatPair& host_format = host_formats_[i];
|
|
if (host_format.format_unsigned.load_shader != kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[host_format.format_unsigned.load_shader] = true;
|
|
}
|
|
if (host_format.format_signed.load_shader != kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[host_format.format_signed.load_shader] = true;
|
|
}
|
|
}
|
|
if (kHostFormatGBGRUnaligned.format_unsigned.load_shader !=
|
|
kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[kHostFormatGBGRUnaligned.format_unsigned.load_shader] =
|
|
true;
|
|
}
|
|
if (kHostFormatGBGRUnaligned.format_signed.load_shader !=
|
|
kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[kHostFormatGBGRUnaligned.format_signed.load_shader] =
|
|
true;
|
|
}
|
|
if (kHostFormatBGRGUnaligned.format_unsigned.load_shader !=
|
|
kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[kHostFormatBGRGUnaligned.format_unsigned.load_shader] =
|
|
true;
|
|
}
|
|
if (kHostFormatBGRGUnaligned.format_signed.load_shader !=
|
|
kLoadShaderIndexUnknown) {
|
|
load_shaders_needed[kHostFormatBGRGUnaligned.format_signed.load_shader] =
|
|
true;
|
|
}
|
|
|
|
std::pair<const uint32_t*, size_t> load_shader_code[kLoadShaderCount] = {};
|
|
load_shader_code[kLoadShaderIndex8bpb] = std::make_pair(
|
|
shaders::texture_load_8bpb_cs, sizeof(shaders::texture_load_8bpb_cs));
|
|
load_shader_code[kLoadShaderIndex16bpb] = std::make_pair(
|
|
shaders::texture_load_16bpb_cs, sizeof(shaders::texture_load_16bpb_cs));
|
|
load_shader_code[kLoadShaderIndex32bpb] = std::make_pair(
|
|
shaders::texture_load_32bpb_cs, sizeof(shaders::texture_load_32bpb_cs));
|
|
load_shader_code[kLoadShaderIndex64bpb] = std::make_pair(
|
|
shaders::texture_load_64bpb_cs, sizeof(shaders::texture_load_64bpb_cs));
|
|
load_shader_code[kLoadShaderIndex128bpb] = std::make_pair(
|
|
shaders::texture_load_128bpb_cs, sizeof(shaders::texture_load_128bpb_cs));
|
|
load_shader_code[kLoadShaderIndexR5G5B5A1ToB5G5R5A1] =
|
|
std::make_pair(shaders::texture_load_r5g5b5a1_b5g5r5a1_cs,
|
|
sizeof(shaders::texture_load_r5g5b5a1_b5g5r5a1_cs));
|
|
load_shader_code[kLoadShaderIndexR5G6B5ToB5G6R5] =
|
|
std::make_pair(shaders::texture_load_r5g6b5_b5g6r5_cs,
|
|
sizeof(shaders::texture_load_r5g6b5_b5g6r5_cs));
|
|
load_shader_code[kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle] =
|
|
std::make_pair(
|
|
shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs,
|
|
sizeof(shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_cs));
|
|
load_shader_code[kLoadShaderIndexRGBA4ToARGB4] =
|
|
std::make_pair(shaders::texture_load_r4g4b4a4_a4r4g4b4_cs,
|
|
sizeof(shaders::texture_load_r4g4b4a4_a4r4g4b4_cs));
|
|
load_shader_code[kLoadShaderIndexGBGR8ToRGB8] =
|
|
std::make_pair(shaders::texture_load_gbgr8_rgb8_cs,
|
|
sizeof(shaders::texture_load_gbgr8_rgb8_cs));
|
|
load_shader_code[kLoadShaderIndexBGRG8ToRGB8] =
|
|
std::make_pair(shaders::texture_load_bgrg8_rgb8_cs,
|
|
sizeof(shaders::texture_load_bgrg8_rgb8_cs));
|
|
load_shader_code[kLoadShaderIndexR10G11B11ToRGBA16] =
|
|
std::make_pair(shaders::texture_load_r10g11b11_rgba16_cs,
|
|
sizeof(shaders::texture_load_r10g11b11_rgba16_cs));
|
|
load_shader_code[kLoadShaderIndexR10G11B11ToRGBA16SNorm] =
|
|
std::make_pair(shaders::texture_load_r10g11b11_rgba16_snorm_cs,
|
|
sizeof(shaders::texture_load_r10g11b11_rgba16_snorm_cs));
|
|
load_shader_code[kLoadShaderIndexR11G11B10ToRGBA16] =
|
|
std::make_pair(shaders::texture_load_r11g11b10_rgba16_cs,
|
|
sizeof(shaders::texture_load_r11g11b10_rgba16_cs));
|
|
load_shader_code[kLoadShaderIndexR11G11B10ToRGBA16SNorm] =
|
|
std::make_pair(shaders::texture_load_r11g11b10_rgba16_snorm_cs,
|
|
sizeof(shaders::texture_load_r11g11b10_rgba16_snorm_cs));
|
|
load_shader_code[kLoadShaderIndexR16UNormToFloat] =
|
|
std::make_pair(shaders::texture_load_r16_unorm_float_cs,
|
|
sizeof(shaders::texture_load_r16_unorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexR16SNormToFloat] =
|
|
std::make_pair(shaders::texture_load_r16_snorm_float_cs,
|
|
sizeof(shaders::texture_load_r16_snorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexRG16UNormToFloat] =
|
|
std::make_pair(shaders::texture_load_rg16_unorm_float_cs,
|
|
sizeof(shaders::texture_load_rg16_unorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexRG16SNormToFloat] =
|
|
std::make_pair(shaders::texture_load_rg16_snorm_float_cs,
|
|
sizeof(shaders::texture_load_rg16_snorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexRGBA16UNormToFloat] =
|
|
std::make_pair(shaders::texture_load_rgba16_unorm_float_cs,
|
|
sizeof(shaders::texture_load_rgba16_unorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexRGBA16SNormToFloat] =
|
|
std::make_pair(shaders::texture_load_rgba16_snorm_float_cs,
|
|
sizeof(shaders::texture_load_rgba16_snorm_float_cs));
|
|
load_shader_code[kLoadShaderIndexDXT1ToRGBA8] =
|
|
std::make_pair(shaders::texture_load_dxt1_rgba8_cs,
|
|
sizeof(shaders::texture_load_dxt1_rgba8_cs));
|
|
load_shader_code[kLoadShaderIndexDXT3ToRGBA8] =
|
|
std::make_pair(shaders::texture_load_dxt3_rgba8_cs,
|
|
sizeof(shaders::texture_load_dxt3_rgba8_cs));
|
|
load_shader_code[kLoadShaderIndexDXT5ToRGBA8] =
|
|
std::make_pair(shaders::texture_load_dxt5_rgba8_cs,
|
|
sizeof(shaders::texture_load_dxt5_rgba8_cs));
|
|
load_shader_code[kLoadShaderIndexDXNToRG8] =
|
|
std::make_pair(shaders::texture_load_dxn_rg8_cs,
|
|
sizeof(shaders::texture_load_dxn_rg8_cs));
|
|
load_shader_code[kLoadShaderIndexDXT3A] = std::make_pair(
|
|
shaders::texture_load_dxt3a_cs, sizeof(shaders::texture_load_dxt3a_cs));
|
|
load_shader_code[kLoadShaderIndexDXT3AAs1111ToARGB4] =
|
|
std::make_pair(shaders::texture_load_dxt3aas1111_argb4_cs,
|
|
sizeof(shaders::texture_load_dxt3aas1111_argb4_cs));
|
|
load_shader_code[kLoadShaderIndexDXT5AToR8] =
|
|
std::make_pair(shaders::texture_load_dxt5a_r8_cs,
|
|
sizeof(shaders::texture_load_dxt5a_r8_cs));
|
|
load_shader_code[kLoadShaderIndexCTX1] = std::make_pair(
|
|
shaders::texture_load_ctx1_cs, sizeof(shaders::texture_load_ctx1_cs));
|
|
load_shader_code[kLoadShaderIndexDepthUnorm] =
|
|
std::make_pair(shaders::texture_load_depth_unorm_cs,
|
|
sizeof(shaders::texture_load_depth_unorm_cs));
|
|
load_shader_code[kLoadShaderIndexDepthFloat] =
|
|
std::make_pair(shaders::texture_load_depth_float_cs,
|
|
sizeof(shaders::texture_load_depth_float_cs));
|
|
std::pair<const uint32_t*, size_t> load_shader_code_scaled[kLoadShaderCount] =
|
|
{};
|
|
if (IsDrawResolutionScaled()) {
|
|
load_shader_code_scaled[kLoadShaderIndex8bpb] =
|
|
std::make_pair(shaders::texture_load_8bpb_scaled_cs,
|
|
sizeof(shaders::texture_load_8bpb_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndex16bpb] =
|
|
std::make_pair(shaders::texture_load_16bpb_scaled_cs,
|
|
sizeof(shaders::texture_load_16bpb_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndex32bpb] =
|
|
std::make_pair(shaders::texture_load_32bpb_scaled_cs,
|
|
sizeof(shaders::texture_load_32bpb_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndex64bpb] =
|
|
std::make_pair(shaders::texture_load_64bpb_scaled_cs,
|
|
sizeof(shaders::texture_load_64bpb_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndex128bpb] =
|
|
std::make_pair(shaders::texture_load_128bpb_scaled_cs,
|
|
sizeof(shaders::texture_load_128bpb_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR5G5B5A1ToB5G5R5A1] =
|
|
std::make_pair(
|
|
shaders::texture_load_r5g5b5a1_b5g5r5a1_scaled_cs,
|
|
sizeof(shaders::texture_load_r5g5b5a1_b5g5r5a1_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR5G6B5ToB5G6R5] =
|
|
std::make_pair(shaders::texture_load_r5g6b5_b5g6r5_scaled_cs,
|
|
sizeof(shaders::texture_load_r5g6b5_b5g6r5_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR5G5B6ToB5G6R5WithRBGASwizzle] =
|
|
std::make_pair(
|
|
shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs,
|
|
sizeof(shaders::texture_load_r5g5b6_b5g6r5_swizzle_rbga_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexRGBA4ToARGB4] = std::make_pair(
|
|
shaders::texture_load_r4g4b4a4_a4r4g4b4_scaled_cs,
|
|
sizeof(shaders::texture_load_r4g4b4a4_a4r4g4b4_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR10G11B11ToRGBA16] = std::make_pair(
|
|
shaders::texture_load_r10g11b11_rgba16_scaled_cs,
|
|
sizeof(shaders::texture_load_r10g11b11_rgba16_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR10G11B11ToRGBA16SNorm] =
|
|
std::make_pair(
|
|
shaders::texture_load_r10g11b11_rgba16_snorm_scaled_cs,
|
|
sizeof(shaders::texture_load_r10g11b11_rgba16_snorm_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR11G11B10ToRGBA16] = std::make_pair(
|
|
shaders::texture_load_r11g11b10_rgba16_scaled_cs,
|
|
sizeof(shaders::texture_load_r11g11b10_rgba16_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR11G11B10ToRGBA16SNorm] =
|
|
std::make_pair(
|
|
shaders::texture_load_r11g11b10_rgba16_snorm_scaled_cs,
|
|
sizeof(shaders::texture_load_r11g11b10_rgba16_snorm_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR16UNormToFloat] =
|
|
std::make_pair(shaders::texture_load_r16_unorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_r16_unorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexR16SNormToFloat] =
|
|
std::make_pair(shaders::texture_load_r16_snorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_r16_snorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexRG16UNormToFloat] = std::make_pair(
|
|
shaders::texture_load_rg16_unorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_rg16_unorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexRG16SNormToFloat] = std::make_pair(
|
|
shaders::texture_load_rg16_snorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_rg16_snorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexRGBA16UNormToFloat] =
|
|
std::make_pair(
|
|
shaders::texture_load_rgba16_unorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_rgba16_unorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexRGBA16SNormToFloat] =
|
|
std::make_pair(
|
|
shaders::texture_load_rgba16_snorm_float_scaled_cs,
|
|
sizeof(shaders::texture_load_rgba16_snorm_float_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexDepthUnorm] =
|
|
std::make_pair(shaders::texture_load_depth_unorm_scaled_cs,
|
|
sizeof(shaders::texture_load_depth_unorm_scaled_cs));
|
|
load_shader_code_scaled[kLoadShaderIndexDepthFloat] =
|
|
std::make_pair(shaders::texture_load_depth_float_scaled_cs,
|
|
sizeof(shaders::texture_load_depth_float_scaled_cs));
|
|
}
|
|
|
|
for (size_t i = 0; i < kLoadShaderCount; ++i) {
|
|
if (!load_shaders_needed[i]) {
|
|
continue;
|
|
}
|
|
const std::pair<const uint32_t*, size_t>& current_load_shader_code =
|
|
load_shader_code[i];
|
|
assert_not_null(current_load_shader_code.first);
|
|
load_pipelines_[i] = ui::vulkan::util::CreateComputePipeline(
|
|
vulkan_device, load_pipeline_layout_, current_load_shader_code.first,
|
|
current_load_shader_code.second);
|
|
if (load_pipelines_[i] == VK_NULL_HANDLE) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to create the texture loading pipeline "
|
|
"for shader {}",
|
|
i);
|
|
return false;
|
|
}
|
|
if (IsDrawResolutionScaled()) {
|
|
const std::pair<const uint32_t*, size_t>&
|
|
current_load_shader_code_scaled = load_shader_code_scaled[i];
|
|
if (current_load_shader_code_scaled.first) {
|
|
load_pipelines_scaled_[i] = ui::vulkan::util::CreateComputePipeline(
|
|
vulkan_device, load_pipeline_layout_,
|
|
current_load_shader_code_scaled.first,
|
|
current_load_shader_code_scaled.second);
|
|
if (load_pipelines_scaled_[i] == VK_NULL_HANDLE) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to create the resolution-scaled "
|
|
"texture loading pipeline for shader {}",
|
|
i);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Null images as a replacement for unneeded bindings and for bindings for
|
|
// which the real image hasn't been created.
|
|
// TODO(Triang3l): Use VK_EXT_robustness2 null descriptors.
|
|
|
|
VkImageCreateInfo null_image_create_info;
|
|
null_image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
null_image_create_info.pNext = nullptr;
|
|
null_image_create_info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
null_image_create_info.imageType = VK_IMAGE_TYPE_2D;
|
|
// Four components to return (0, 0, 0, 0).
|
|
// TODO(Triang3l): Find the return value for invalid texture fetch constants
|
|
// on the real hardware.
|
|
null_image_create_info.format = VK_FORMAT_R8G8B8A8_UNORM;
|
|
null_image_create_info.extent.width = 1;
|
|
null_image_create_info.extent.height = 1;
|
|
null_image_create_info.extent.depth = 1;
|
|
null_image_create_info.mipLevels = 1;
|
|
null_image_create_info.arrayLayers = 6;
|
|
null_image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
null_image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
null_image_create_info.usage =
|
|
VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
null_image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
null_image_create_info.queueFamilyIndexCount = 0;
|
|
null_image_create_info.pQueueFamilyIndices = nullptr;
|
|
null_image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
if (dfn.vkCreateImage(device, &null_image_create_info, nullptr,
|
|
&null_image_2d_array_cube_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to create the null 2D array and cube "
|
|
"image");
|
|
return false;
|
|
}
|
|
|
|
null_image_create_info.flags &= ~VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
|
|
null_image_create_info.imageType = VK_IMAGE_TYPE_3D;
|
|
null_image_create_info.arrayLayers = 1;
|
|
if (dfn.vkCreateImage(device, &null_image_create_info, nullptr,
|
|
&null_image_3d_) != VK_SUCCESS) {
|
|
XELOGE("VulkanTextureCache: Failed to create the null 3D image");
|
|
return false;
|
|
}
|
|
|
|
VkMemoryRequirements null_image_memory_requirements_2d_array_cube_;
|
|
dfn.vkGetImageMemoryRequirements(
|
|
device, null_image_2d_array_cube_,
|
|
&null_image_memory_requirements_2d_array_cube_);
|
|
VkMemoryRequirements null_image_memory_requirements_3d_;
|
|
dfn.vkGetImageMemoryRequirements(device, null_image_3d_,
|
|
&null_image_memory_requirements_3d_);
|
|
uint32_t null_image_memory_type_common = ui::vulkan::util::ChooseMemoryType(
|
|
vulkan_device->memory_types(),
|
|
null_image_memory_requirements_2d_array_cube_.memoryTypeBits &
|
|
null_image_memory_requirements_3d_.memoryTypeBits,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
|
|
if (null_image_memory_type_common != UINT32_MAX) {
|
|
// Place both null images in one memory allocation because maximum total
|
|
// memory allocation count is limited.
|
|
VkDeviceSize null_image_memory_offset_3d_ =
|
|
xe::align(null_image_memory_requirements_2d_array_cube_.size,
|
|
std::max(null_image_memory_requirements_3d_.alignment,
|
|
VkDeviceSize(1)));
|
|
VkMemoryAllocateInfo null_image_memory_allocate_info;
|
|
null_image_memory_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
null_image_memory_allocate_info.pNext = nullptr;
|
|
null_image_memory_allocate_info.allocationSize =
|
|
null_image_memory_offset_3d_ + null_image_memory_requirements_3d_.size;
|
|
null_image_memory_allocate_info.memoryTypeIndex =
|
|
null_image_memory_type_common;
|
|
if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
|
|
&null_images_memory_[0]) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to allocate the memory for the null "
|
|
"images");
|
|
return false;
|
|
}
|
|
if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_,
|
|
null_images_memory_[0], 0) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to bind the memory to the null 2D array "
|
|
"and cube image");
|
|
return false;
|
|
}
|
|
if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[0],
|
|
null_image_memory_offset_3d_) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to bind the memory to the null 3D image");
|
|
return false;
|
|
}
|
|
} else {
|
|
// Place each null image in separate allocations.
|
|
const uint32_t null_image_memory_type_2d_array_cube =
|
|
ui::vulkan::util::ChooseMemoryType(
|
|
vulkan_device->memory_types(),
|
|
null_image_memory_requirements_2d_array_cube_.memoryTypeBits,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
|
|
const uint32_t null_image_memory_type_3d =
|
|
ui::vulkan::util::ChooseMemoryType(
|
|
vulkan_device->memory_types(),
|
|
null_image_memory_requirements_3d_.memoryTypeBits,
|
|
ui::vulkan::util::MemoryPurpose::kDeviceLocal);
|
|
if (null_image_memory_type_2d_array_cube == UINT32_MAX ||
|
|
null_image_memory_type_3d == UINT32_MAX) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to get the memory types for the null "
|
|
"images");
|
|
return false;
|
|
}
|
|
|
|
VkMemoryAllocateInfo null_image_memory_allocate_info;
|
|
VkMemoryAllocateInfo* null_image_memory_allocate_info_last =
|
|
&null_image_memory_allocate_info;
|
|
null_image_memory_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
null_image_memory_allocate_info.pNext = nullptr;
|
|
null_image_memory_allocate_info.allocationSize =
|
|
null_image_memory_requirements_2d_array_cube_.size;
|
|
null_image_memory_allocate_info.memoryTypeIndex =
|
|
null_image_memory_type_2d_array_cube;
|
|
VkMemoryDedicatedAllocateInfo null_image_memory_dedicated_allocate_info;
|
|
if (vulkan_device->extensions().ext_1_1_KHR_dedicated_allocation) {
|
|
null_image_memory_allocate_info_last->pNext =
|
|
&null_image_memory_dedicated_allocate_info;
|
|
null_image_memory_allocate_info_last =
|
|
reinterpret_cast<VkMemoryAllocateInfo*>(
|
|
&null_image_memory_dedicated_allocate_info);
|
|
null_image_memory_dedicated_allocate_info.sType =
|
|
VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO;
|
|
null_image_memory_dedicated_allocate_info.pNext = nullptr;
|
|
null_image_memory_dedicated_allocate_info.image =
|
|
null_image_2d_array_cube_;
|
|
null_image_memory_dedicated_allocate_info.buffer = VK_NULL_HANDLE;
|
|
}
|
|
if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
|
|
&null_images_memory_[0]) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to allocate the memory for the null 2D "
|
|
"array and cube image");
|
|
return false;
|
|
}
|
|
if (dfn.vkBindImageMemory(device, null_image_2d_array_cube_,
|
|
null_images_memory_[0], 0) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to bind the memory to the null 2D array "
|
|
"and cube image");
|
|
return false;
|
|
}
|
|
|
|
null_image_memory_allocate_info.allocationSize =
|
|
null_image_memory_requirements_3d_.size;
|
|
null_image_memory_allocate_info.memoryTypeIndex = null_image_memory_type_3d;
|
|
null_image_memory_dedicated_allocate_info.image = null_image_3d_;
|
|
if (dfn.vkAllocateMemory(device, &null_image_memory_allocate_info, nullptr,
|
|
&null_images_memory_[1]) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to allocate the memory for the null 3D "
|
|
"image");
|
|
return false;
|
|
}
|
|
if (dfn.vkBindImageMemory(device, null_image_3d_, null_images_memory_[1],
|
|
0) != VK_SUCCESS) {
|
|
XELOGE(
|
|
"VulkanTextureCache: Failed to bind the memory to the null 3D image");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
VkImageViewCreateInfo null_image_view_create_info;
|
|
null_image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
null_image_view_create_info.pNext = nullptr;
|
|
null_image_view_create_info.flags = 0;
|
|
null_image_view_create_info.image = null_image_2d_array_cube_;
|
|
null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
|
null_image_view_create_info.format = null_image_create_info.format;
|
|
// TODO(Triang3l): Find the return value for invalid texture fetch constants
|
|
// on the real hardware.
|
|
// Micro-optimization if this has any effect on the host GPU at all, use only
|
|
// constant components instead of the real texels. The image will be cleared
|
|
// to (0, 0, 0, 0) anyway.
|
|
VkComponentSwizzle null_image_view_swizzle =
|
|
device_properties.imageViewFormatSwizzle ? VK_COMPONENT_SWIZZLE_ZERO
|
|
: VK_COMPONENT_SWIZZLE_IDENTITY;
|
|
null_image_view_create_info.components.r = null_image_view_swizzle;
|
|
null_image_view_create_info.components.g = null_image_view_swizzle;
|
|
null_image_view_create_info.components.b = null_image_view_swizzle;
|
|
null_image_view_create_info.components.a = null_image_view_swizzle;
|
|
null_image_view_create_info.subresourceRange =
|
|
ui::vulkan::util::InitializeSubresourceRange(
|
|
VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, 1);
|
|
if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
|
|
&null_image_view_2d_array_) != VK_SUCCESS) {
|
|
XELOGE("VulkanTextureCache: Failed to create the null 2D array image view");
|
|
return false;
|
|
}
|
|
null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
|
null_image_view_create_info.subresourceRange.layerCount = 6;
|
|
if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
|
|
&null_image_view_cube_) != VK_SUCCESS) {
|
|
XELOGE("VulkanTextureCache: Failed to create the null cube image view");
|
|
return false;
|
|
}
|
|
null_image_view_create_info.image = null_image_3d_;
|
|
null_image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_3D;
|
|
null_image_view_create_info.subresourceRange.layerCount = 1;
|
|
if (dfn.vkCreateImageView(device, &null_image_view_create_info, nullptr,
|
|
&null_image_view_3d_) != VK_SUCCESS) {
|
|
XELOGE("VulkanTextureCache: Failed to create the null 3D image view");
|
|
return false;
|
|
}
|
|
|
|
null_images_cleared_ = false;
|
|
|
|
// Samplers.
|
|
|
|
// Some MoltenVK devices have a maximum of 2048, 1024, or even 96 samplers,
|
|
// below Vulkan's minimum requirement of 4000.
|
|
// Assuming that the current VulkanTextureCache is the only one on this
|
|
// VkDevice (true in a regular emulation scenario), so taking over all the
|
|
// allocation slots exclusively.
|
|
// Also leaving a few slots for use by things like overlay applications.
|
|
sampler_max_count_ = device_properties.maxSamplerAllocationCount -
|
|
ui::vulkan::UISamplers::kSamplerCount - 16;
|
|
|
|
if (device_properties.samplerAnisotropy) {
|
|
max_anisotropy_ = xenos::AnisoFilter(
|
|
uint32_t(xenos::AnisoFilter::kMax_1_1) +
|
|
(31 -
|
|
xe::lzcnt(uint32_t(std::min(
|
|
16.0f, std::max(1.0f, device_properties.maxSamplerAnisotropy))))));
|
|
} else {
|
|
max_anisotropy_ = xenos::AnisoFilter::kDisabled;
|
|
}
|
|
|
|
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<int>(result));
|
|
return false;
|
|
}
|
|
|
|
new_buffer.range_start_scaled = buffer_start;
|
|
new_buffer.range_length_scaled = buffer_size;
|
|
|
|
scaled_resolve_buffers_.push_back(new_buffer);
|
|
scaled_resolve_current_range_start_scaled_ = buffer_start;
|
|
scaled_resolve_current_range_length_scaled_ = buffer_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool VulkanTextureCache::MakeScaledResolveRangeCurrent(
|
|
uint32_t start_unscaled, uint32_t length_unscaled,
|
|
uint32_t length_scaled_alignment_log2) {
|
|
if (!IsDrawResolutionScaled()) {
|
|
return false;
|
|
}
|
|
|
|
// 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
|