Files
Xenia-Canary/src/xenia/gpu/vulkan/vulkan_texture_cache.h
2022-07-03 19:40:48 +03:00

364 lines
14 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. *
******************************************************************************
*/
#ifndef XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_
#define XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_
#include <array>
#include <memory>
#include <unordered_map>
#include <utility>
#include "xenia/base/hash.h"
#include "xenia/gpu/texture_cache.h"
#include "xenia/gpu/vulkan/vulkan_shader.h"
#include "xenia/gpu/vulkan/vulkan_shared_memory.h"
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace gpu {
namespace vulkan {
class VulkanCommandProcessor;
class VulkanTextureCache final : public TextureCache {
public:
// Sampler parameters that can be directly converted to a host sampler or used
// for checking whether samplers bindings are up to date.
union SamplerParameters {
uint32_t value;
struct {
xenos::ClampMode clamp_x : 3; // 3
xenos::ClampMode clamp_y : 3; // 6
xenos::ClampMode clamp_z : 3; // 9
xenos::BorderColor border_color : 2; // 11
uint32_t mag_linear : 1; // 12
uint32_t min_linear : 1; // 13
uint32_t mip_linear : 1; // 14
xenos::AnisoFilter aniso_filter : 3; // 17
uint32_t mip_min_level : 4; // 21
uint32_t mip_base_map : 1; // 22
// Maximum mip level is in the texture resource itself, but mip_base_map
// can be used to limit fetching to mip_min_level.
};
SamplerParameters() : value(0) { static_assert_size(*this, sizeof(value)); }
struct Hasher {
size_t operator()(const SamplerParameters& parameters) const {
return std::hash<uint32_t>{}(parameters.value);
}
};
bool operator==(const SamplerParameters& parameters) const {
return value == parameters.value;
}
bool operator!=(const SamplerParameters& parameters) const {
return value != parameters.value;
}
};
// Transient descriptor set layouts must be initialized in the command
// processor.
static std::unique_ptr<VulkanTextureCache> Create(
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) {
std::unique_ptr<VulkanTextureCache> texture_cache(new VulkanTextureCache(
register_file, shared_memory, draw_resolution_scale_x,
draw_resolution_scale_y, command_processor,
guest_shader_pipeline_stages));
if (!texture_cache->Initialize()) {
return nullptr;
}
return std::move(texture_cache);
}
~VulkanTextureCache();
void BeginSubmission(uint64_t new_submission_index) override;
// Must be called within a frame - creates and untiles textures needed by
// shaders, and enqueues transitioning them into the sampled usage. This may
// bind compute pipelines (notifying the command processor about that), and
// also since it may insert deferred barriers, before flushing the barriers
// preceding host GPU work.
void RequestTextures(uint32_t used_texture_mask) override;
VkImageView GetActiveBindingOrNullImageView(uint32_t fetch_constant_index,
xenos::FetchOpDimension dimension,
bool is_signed) const;
SamplerParameters GetSamplerParameters(
const VulkanShader::SamplerBinding& binding) const;
// Must be called for every used sampler at least once in a single submission,
// and a submission must be open for this to be callable.
// Returns:
// - The sampler, if obtained successfully - and increases its last usage
// submission index - and has_overflown_out = false.
// - VK_NULL_HANDLE and has_overflown_out = true if there's a total sampler
// count overflow in a submission that potentially hasn't completed yet.
// - VK_NULL_HANDLE and has_overflown_out = false in case of a general failure
// to create a sampler.
VkSampler UseSampler(SamplerParameters parameters, bool& has_overflown_out);
// Returns the submission index to await (may be the current submission in
// case of an overflow within a single submission - in this case, it must be
// ended, and a new one must be started) in case of sampler count overflow, so
// samplers may be freed, and UseSamplers may take their slots.
uint64_t GetSubmissionToAwaitOnSamplerOverflow(
uint32_t overflowed_sampler_count) const;
// Returns the 2D view of the front buffer texture (for fragment shader
// reading - the barrier will be pushed in the command processor if needed),
// or VK_NULL_HANDLE in case of failure. May call LoadTextureData.
VkImageView RequestSwapTexture(uint32_t& width_scaled_out,
uint32_t& height_scaled_out,
xenos::TextureFormat& format_out);
protected:
bool IsSignedVersionSeparateForFormat(TextureKey key) const override;
uint32_t GetHostFormatSwizzle(TextureKey key) const override;
uint32_t GetMaxHostTextureWidthHeight(
xenos::DataDimension dimension) const override;
uint32_t GetMaxHostTextureDepthOrArraySize(
xenos::DataDimension dimension) const override;
std::unique_ptr<Texture> CreateTexture(TextureKey key) override;
bool LoadTextureDataFromResidentMemoryImpl(Texture& texture, bool load_base,
bool load_mips) override;
void UpdateTextureBindingsImpl(uint32_t fetch_constant_mask) override;
private:
enum LoadDescriptorSetIndex {
kLoadDescriptorSetIndexDestination,
kLoadDescriptorSetIndexSource,
kLoadDescriptorSetIndexConstants,
kLoadDescriptorSetCount,
};
struct HostFormat {
LoadShaderIndex load_shader;
// Do NOT add integer formats to this - they are not filterable, can only be
// read with ImageFetch, not ImageSample! If any game is seen using
// num_format 1 for fixed-point formats (for floating-point, it's normally
// set to 1 though), add a constant buffer containing multipliers for the
// textures and multiplication to the tfetch implementation.
VkFormat format;
// Whether the format is block-compressed on the host (the host block size
// matches the guest format block size in this case), and isn't decompressed
// on load.
bool block_compressed;
// Set up dynamically based on what's supported by the device.
bool linear_filterable;
};
struct HostFormatPair {
HostFormat format_unsigned;
HostFormat format_signed;
// Mapping of Xenos swizzle components to Vulkan format components.
uint32_t swizzle;
// Whether the unsigned and the signed formats are compatible for one image
// and the same image data (on a portability subset device, this should also
// take imageViewFormatReinterpretation into account).
bool unsigned_signed_compatible;
};
class VulkanTexture final : public Texture {
public:
enum class Usage {
kUndefined,
kTransferDestination,
kGuestShaderSampled,
kSwapSampled,
};
// Takes ownership of the image and its memory.
explicit VulkanTexture(VulkanTextureCache& texture_cache,
const TextureKey& key, VkImage image,
VmaAllocation allocation);
~VulkanTexture();
VkImage image() const { return image_; }
// Doesn't transition (the caller must insert the barrier).
Usage SetUsage(Usage new_usage) {
Usage old_usage = usage_;
usage_ = new_usage;
return old_usage;
}
VkImageView GetView(bool is_signed, uint32_t host_swizzle,
bool is_array = true);
private:
union ViewKey {
uint32_t key;
struct {
uint32_t is_signed_separate_view : 1;
uint32_t host_swizzle : 12;
uint32_t is_array : 1;
};
ViewKey() : key(0) { static_assert_size(*this, sizeof(key)); }
struct Hasher {
size_t operator()(const ViewKey& key) const {
return std::hash<decltype(key.key)>{}(key.key);
}
};
bool operator==(const ViewKey& other_key) const {
return key == other_key.key;
}
bool operator!=(const ViewKey& other_key) const {
return !(*this == other_key);
}
};
static constexpr VkComponentSwizzle GetComponentSwizzle(
uint32_t texture_swizzle, uint32_t component_index) {
xenos::XE_GPU_TEXTURE_SWIZZLE texture_component_swizzle =
xenos::XE_GPU_TEXTURE_SWIZZLE(
(texture_swizzle >> (3 * component_index)) & 0b111);
if (texture_component_swizzle ==
xenos::XE_GPU_TEXTURE_SWIZZLE(component_index)) {
// The portability subset requires all swizzles to be IDENTITY, return
// IDENTITY specifically, not R, G, B, A.
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
switch (texture_component_swizzle) {
case xenos::XE_GPU_TEXTURE_SWIZZLE_R:
return VK_COMPONENT_SWIZZLE_R;
case xenos::XE_GPU_TEXTURE_SWIZZLE_G:
return VK_COMPONENT_SWIZZLE_G;
case xenos::XE_GPU_TEXTURE_SWIZZLE_B:
return VK_COMPONENT_SWIZZLE_B;
case xenos::XE_GPU_TEXTURE_SWIZZLE_A:
return VK_COMPONENT_SWIZZLE_A;
case xenos::XE_GPU_TEXTURE_SWIZZLE_0:
return VK_COMPONENT_SWIZZLE_ZERO;
case xenos::XE_GPU_TEXTURE_SWIZZLE_1:
return VK_COMPONENT_SWIZZLE_ONE;
default:
// An invalid value.
return VK_COMPONENT_SWIZZLE_IDENTITY;
}
}
VkImage image_;
VmaAllocation allocation_;
Usage usage_ = Usage::kUndefined;
std::unordered_map<ViewKey, VkImageView, ViewKey::Hasher> views_;
};
struct VulkanTextureBinding {
VkImageView image_view_unsigned;
VkImageView image_view_signed;
VulkanTextureBinding() { Reset(); }
void Reset() {
image_view_unsigned = VK_NULL_HANDLE;
image_view_signed = VK_NULL_HANDLE;
}
};
struct Sampler {
VkSampler sampler;
uint64_t last_usage_submission;
std::pair<const SamplerParameters, Sampler>* used_previous;
std::pair<const SamplerParameters, Sampler>* used_next;
};
static constexpr bool AreDimensionsCompatible(
xenos::FetchOpDimension binding_dimension,
xenos::DataDimension resource_dimension) {
switch (binding_dimension) {
case xenos::FetchOpDimension::k1D:
case xenos::FetchOpDimension::k2D:
return resource_dimension == xenos::DataDimension::k1D ||
resource_dimension == xenos::DataDimension::k2DOrStacked;
case xenos::FetchOpDimension::k3DOrStacked:
return resource_dimension == xenos::DataDimension::k3D;
case xenos::FetchOpDimension::kCube:
return resource_dimension == xenos::DataDimension::kCube;
default:
return false;
}
}
explicit 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);
bool Initialize();
const HostFormatPair& GetHostFormatPair(TextureKey key) const;
void GetTextureUsageMasks(VulkanTexture::Usage usage,
VkPipelineStageFlags& stage_mask,
VkAccessFlags& access_mask, VkImageLayout& layout);
xenos::ClampMode NormalizeClampMode(xenos::ClampMode clamp_mode) const;
VulkanCommandProcessor& command_processor_;
VkPipelineStageFlags guest_shader_pipeline_stages_;
// Using the Vulkan Memory Allocator because texture count in games is
// naturally pretty much unbounded, while Vulkan implementations, especially
// on Windows versions before 10, may have an allocation count limit as low as
// 4096.
VmaAllocator vma_allocator_ = VK_NULL_HANDLE;
static const HostFormatPair kBestHostFormats[64];
static const HostFormatPair kHostFormatGBGRUnaligned;
static const HostFormatPair kHostFormatBGRGUnaligned;
HostFormatPair host_formats_[64];
VkPipelineLayout load_pipeline_layout_ = VK_NULL_HANDLE;
std::array<VkPipeline, kLoadShaderCount> load_pipelines_{};
std::array<VkPipeline, kLoadShaderCount> load_pipelines_scaled_{};
// If both images can be placed in the same allocation, it's one allocation,
// otherwise it's two separate.
std::array<VkDeviceMemory, 2> null_images_memory_{};
VkImage null_image_2d_array_cube_ = VK_NULL_HANDLE;
VkImage null_image_3d_ = VK_NULL_HANDLE;
VkImageView null_image_view_2d_array_ = VK_NULL_HANDLE;
VkImageView null_image_view_cube_ = VK_NULL_HANDLE;
VkImageView null_image_view_3d_ = VK_NULL_HANDLE;
bool null_images_cleared_ = false;
std::array<VulkanTextureBinding, xenos::kTextureFetchConstantCount>
vulkan_texture_bindings_;
uint32_t sampler_max_count_;
xenos::AnisoFilter max_anisotropy_;
std::unordered_map<SamplerParameters, Sampler, SamplerParameters::Hasher>
samplers_;
std::pair<const SamplerParameters, Sampler>* sampler_used_first_ = nullptr;
std::pair<const SamplerParameters, Sampler>* sampler_used_last_ = nullptr;
};
} // namespace vulkan
} // namespace gpu
} // namespace xe
#endif // XENIA_GPU_VULKAN_VULKAN_TEXTURE_CACHE_H_