[Vulkan] VMA for textures

This commit is contained in:
Triang3l 2022-07-03 19:40:48 +03:00
parent 636585e0aa
commit 001f64852c
13 changed files with 283 additions and 17 deletions

3
.gitmodules vendored
View file

@ -82,3 +82,6 @@
[submodule "third_party/SPIRV-Tools"]
path = third_party/SPIRV-Tools
url = https://github.com/KhronosGroup/SPIRV-Tools.git
[submodule "third_party/VulkanMemoryAllocator"]
path = third_party/VulkanMemoryAllocator
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git

View file

@ -21,6 +21,7 @@
#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/vulkan_mem_alloc.h"
#include "xenia/ui/vulkan/vulkan_util.h"
namespace xe {
@ -468,6 +469,14 @@ VulkanTextureCache::~VulkanTextureCache() {
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);
if (vma_allocator_ != VK_NULL_HANDLE) {
vmaDestroyAllocator(vma_allocator_);
}
}
void VulkanTextureCache::BeginSubmission(uint64_t new_submission_index) {
@ -1052,21 +1061,19 @@ std::unique_ptr<TextureCache::Texture> VulkanTextureCache::CreateTexture(
image_format_list_create_info.viewFormatCount = 2;
image_format_list_create_info.pViewFormats = formats;
}
// TODO(Triang3l): Suballocate due to the low memory allocation count limit on
// Windows (use VMA or a custom allocator, possibly based on two-level
// segregated fit just like VMA).
VmaAllocationCreateInfo allocation_create_info = {};
allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
VkImage image;
VkDeviceMemory memory;
VkDeviceSize memory_size;
if (!ui::vulkan::util::CreateDedicatedAllocationImage(
provider, image_create_info,
ui::vulkan::util::MemoryPurpose::kDeviceLocal, image, memory, nullptr,
&memory_size)) {
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, memory, memory_size));
new VulkanTexture(*this, key, image, allocation));
}
bool VulkanTextureCache::LoadTextureDataFromResidentMemoryImpl(Texture& texture,
@ -1571,9 +1578,12 @@ void VulkanTextureCache::UpdateTextureBindingsImpl(
VulkanTextureCache::VulkanTexture::VulkanTexture(
VulkanTextureCache& texture_cache, const TextureKey& key, VkImage image,
VkDeviceMemory memory, VkDeviceSize memory_size)
: Texture(texture_cache, key), image_(image), memory_(memory) {
SetHostMemoryUsage(uint64_t(memory_size));
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() {
@ -1586,8 +1596,7 @@ VulkanTextureCache::VulkanTexture::~VulkanTexture() {
for (const auto& view_pair : views_) {
dfn.vkDestroyImageView(device, view_pair.second, nullptr);
}
dfn.vkDestroyImage(device, image_, nullptr);
dfn.vkFreeMemory(device, memory_, nullptr);
vmaDestroyImage(vulkan_texture_cache.vma_allocator_, image_, allocation_);
}
VkImageView VulkanTextureCache::VulkanTexture::GetView(bool is_signed,
@ -1708,6 +1717,13 @@ bool VulkanTextureCache::Initialize() {
device_portability_subset_features =
provider.device_portability_subset_features();
// Vulkan Memory Allocator.
vma_allocator_ = ui::vulkan::CreateVmaAllocator(provider, true);
if (vma_allocator_ == VK_NULL_HANDLE) {
return false;
}
// Image formats.
// Initialize to the best formats.

View file

@ -19,6 +19,7 @@
#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 {
@ -185,7 +186,7 @@ class VulkanTextureCache final : public TextureCache {
// Takes ownership of the image and its memory.
explicit VulkanTexture(VulkanTextureCache& texture_cache,
const TextureKey& key, VkImage image,
VkDeviceMemory memory, VkDeviceSize memory_size);
VmaAllocation allocation);
~VulkanTexture();
VkImage image() const { return image_; }
@ -255,7 +256,7 @@ class VulkanTextureCache final : public TextureCache {
}
VkImage image_;
VkDeviceMemory memory_;
VmaAllocation allocation_;
Usage usage_ = Usage::kUndefined;
@ -317,6 +318,12 @@ class VulkanTextureCache final : public TextureCache {
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;

View file

@ -66,6 +66,7 @@ XE_UI_VULKAN_FUNCTION(vkGetBufferMemoryRequirements)
XE_UI_VULKAN_FUNCTION(vkGetDeviceQueue)
XE_UI_VULKAN_FUNCTION(vkGetFenceStatus)
XE_UI_VULKAN_FUNCTION(vkGetImageMemoryRequirements)
XE_UI_VULKAN_FUNCTION(vkInvalidateMappedMemoryRanges)
XE_UI_VULKAN_FUNCTION(vkMapMemory)
XE_UI_VULKAN_FUNCTION(vkResetCommandPool)
XE_UI_VULKAN_FUNCTION(vkResetDescriptorPool)

View file

@ -0,0 +1,4 @@
// VK_KHR_bind_memory2 functions used in Xenia.
// Promoted to Vulkan 1.1 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkBindBufferMemory2KHR, vkBindBufferMemory2)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkBindImageMemory2KHR, vkBindImageMemory2)

View file

@ -0,0 +1,6 @@
// VK_KHR_get_memory_requirements2 functions used in Xenia.
// Promoted to Vulkan 1.1 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetBufferMemoryRequirements2KHR,
vkGetBufferMemoryRequirements2)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetImageMemoryRequirements2KHR,
vkGetImageMemoryRequirements2)

View file

@ -0,0 +1,6 @@
// VK_KHR_maintenance4 functions used in Xenia.
// Promoted to Vulkan 1.3 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetDeviceBufferMemoryRequirementsKHR,
vkGetDeviceBufferMemoryRequirements)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetDeviceImageMemoryRequirementsKHR,
vkGetDeviceImageMemoryRequirements)

View file

@ -1,4 +1,6 @@
// VK_KHR_get_physical_device_properties2 functions used in Xenia.
// Promoted to Vulkan 1.1 core.
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceMemoryProperties2KHR,
vkGetPhysicalDeviceMemoryProperties2)
XE_UI_VULKAN_FUNCTION_PROMOTED(vkGetPhysicalDeviceProperties2KHR,
vkGetPhysicalDeviceProperties2)

View file

@ -0,0 +1,108 @@
/**
******************************************************************************
* 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. *
******************************************************************************
*/
// Implementing VMA in this translation unit.
#define VMA_IMPLEMENTATION
#include "xenia/ui/vulkan/vulkan_mem_alloc.h"
#include <cstring>
#include "xenia/base/logging.h"
#include "xenia/ui/vulkan/vulkan_provider.h"
namespace xe {
namespace ui {
namespace vulkan {
VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
bool externally_synchronized) {
const VulkanProvider::LibraryFunctions& lfn = provider.lfn();
const VulkanProvider::InstanceFunctions& ifn = provider.ifn();
const VulkanProvider::DeviceFunctions& dfn = provider.dfn();
const VulkanProvider::InstanceExtensions& instance_extensions =
provider.instance_extensions();
const VulkanProvider::DeviceExtensions& device_extensions =
provider.device_extensions();
VmaVulkanFunctions vma_vulkan_functions = {};
VmaAllocatorCreateInfo allocator_create_info = {};
vma_vulkan_functions.vkGetInstanceProcAddr = lfn.vkGetInstanceProcAddr;
vma_vulkan_functions.vkGetDeviceProcAddr = ifn.vkGetDeviceProcAddr;
vma_vulkan_functions.vkGetPhysicalDeviceProperties =
ifn.vkGetPhysicalDeviceProperties;
vma_vulkan_functions.vkGetPhysicalDeviceMemoryProperties =
ifn.vkGetPhysicalDeviceMemoryProperties;
vma_vulkan_functions.vkAllocateMemory = dfn.vkAllocateMemory;
vma_vulkan_functions.vkFreeMemory = dfn.vkFreeMemory;
vma_vulkan_functions.vkMapMemory = dfn.vkMapMemory;
vma_vulkan_functions.vkUnmapMemory = dfn.vkUnmapMemory;
vma_vulkan_functions.vkFlushMappedMemoryRanges =
dfn.vkFlushMappedMemoryRanges;
vma_vulkan_functions.vkInvalidateMappedMemoryRanges =
dfn.vkInvalidateMappedMemoryRanges;
vma_vulkan_functions.vkBindBufferMemory = dfn.vkBindBufferMemory;
vma_vulkan_functions.vkBindImageMemory = dfn.vkBindImageMemory;
vma_vulkan_functions.vkGetBufferMemoryRequirements =
dfn.vkGetBufferMemoryRequirements;
vma_vulkan_functions.vkGetImageMemoryRequirements =
dfn.vkGetImageMemoryRequirements;
vma_vulkan_functions.vkCreateBuffer = dfn.vkCreateBuffer;
vma_vulkan_functions.vkDestroyBuffer = dfn.vkDestroyBuffer;
vma_vulkan_functions.vkCreateImage = dfn.vkCreateImage;
vma_vulkan_functions.vkDestroyImage = dfn.vkDestroyImage;
vma_vulkan_functions.vkCmdCopyBuffer = dfn.vkCmdCopyBuffer;
if (device_extensions.khr_get_memory_requirements2) {
vma_vulkan_functions.vkGetBufferMemoryRequirements2KHR =
dfn.vkGetBufferMemoryRequirements2KHR;
vma_vulkan_functions.vkGetImageMemoryRequirements2KHR =
dfn.vkGetImageMemoryRequirements2KHR;
if (device_extensions.khr_dedicated_allocation) {
allocator_create_info.flags |=
VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
}
}
if (device_extensions.khr_bind_memory2) {
vma_vulkan_functions.vkBindBufferMemory2KHR = dfn.vkBindBufferMemory2KHR;
vma_vulkan_functions.vkBindImageMemory2KHR = dfn.vkBindImageMemory2KHR;
allocator_create_info.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT;
}
if (instance_extensions.khr_get_physical_device_properties2) {
vma_vulkan_functions.vkGetPhysicalDeviceMemoryProperties2KHR =
ifn.vkGetPhysicalDeviceMemoryProperties2KHR;
if (device_extensions.ext_memory_budget) {
allocator_create_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
}
if (device_extensions.khr_maintenance4) {
vma_vulkan_functions.vkGetDeviceImageMemoryRequirements =
dfn.vkGetDeviceImageMemoryRequirementsKHR;
}
if (externally_synchronized) {
allocator_create_info.flags |=
VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT;
}
allocator_create_info.physicalDevice = provider.physical_device();
allocator_create_info.device = provider.device();
allocator_create_info.pVulkanFunctions = &vma_vulkan_functions;
allocator_create_info.instance = provider.instance();
allocator_create_info.vulkanApiVersion =
provider.device_properties().apiVersion;
VmaAllocator allocator;
if (vmaCreateAllocator(&allocator_create_info, &allocator) != VK_SUCCESS) {
XELOGE("Failed to create a Vulkan Memory Allocator instance");
return VK_NULL_HANDLE;
}
return allocator;
}
} // namespace vulkan
} // namespace ui
} // namespace xe

View file

@ -0,0 +1,39 @@
/**
******************************************************************************
* 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_UI_VULKAN_VULKAN_MEM_ALLOC_H_
#define XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_
// Make sure vulkan.h is included from third_party (rather than from the system
// include directory) before vk_mem_alloc.h.
#include "xenia/ui/vulkan/vulkan_provider.h"
#define VMA_STATIC_VULKAN_FUNCTIONS 0
// Work around the pointer nullability completeness warnings on Clang.
#ifndef VMA_NULLABLE
#define VMA_NULLABLE
#endif
#ifndef VMA_NOT_NULL
#define VMA_NOT_NULL
#endif
#include "third_party/VulkanMemoryAllocator/include/vk_mem_alloc.h"
namespace xe {
namespace ui {
namespace vulkan {
VmaAllocator CreateVmaAllocator(const VulkanProvider& provider,
bool externally_synchronized);
} // namespace vulkan
} // namespace ui
} // namespace xe
#endif // XENIA_UI_VULKAN_VULKAN_MEM_ALLOC_H_

View file

@ -700,12 +700,17 @@ bool VulkanProvider::Initialize() {
}
std::memset(&device_extensions_, 0, sizeof(device_extensions_));
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
device_extensions_.khr_bind_memory2 = true;
device_extensions_.khr_dedicated_allocation = true;
device_extensions_.khr_get_memory_requirements2 = true;
device_extensions_.khr_sampler_ycbcr_conversion = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 2, 0)) {
device_extensions_.khr_image_format_list = true;
device_extensions_.khr_shader_float_controls = true;
device_extensions_.khr_spirv_1_4 = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
device_extensions_.khr_maintenance4 = true;
}
}
}
device_extensions_enabled.clear();
@ -716,12 +721,17 @@ bool VulkanProvider::Initialize() {
static const std::pair<const char*, size_t> kUsedDeviceExtensions[] = {
{"VK_EXT_fragment_shader_interlock",
offsetof(DeviceExtensions, ext_fragment_shader_interlock)},
{"VK_EXT_memory_budget", offsetof(DeviceExtensions, ext_memory_budget)},
{"VK_EXT_shader_stencil_export",
offsetof(DeviceExtensions, ext_shader_stencil_export)},
{"VK_KHR_bind_memory2", offsetof(DeviceExtensions, khr_bind_memory2)},
{"VK_KHR_dedicated_allocation",
offsetof(DeviceExtensions, khr_dedicated_allocation)},
{"VK_KHR_get_memory_requirements2",
offsetof(DeviceExtensions, khr_get_memory_requirements2)},
{"VK_KHR_image_format_list",
offsetof(DeviceExtensions, khr_image_format_list)},
{"VK_KHR_maintenance4", offsetof(DeviceExtensions, khr_maintenance4)},
{"VK_KHR_portability_subset",
offsetof(DeviceExtensions, khr_portability_subset)},
// While vkGetPhysicalDeviceFormatProperties should be used to check the
@ -922,6 +932,48 @@ bool VulkanProvider::Initialize() {
}
}
// Extensions - disable the specific extension if failed to get its functions.
if (device_extensions_.khr_bind_memory2) {
bool functions_loaded = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_bind_memory2.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
} else {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_DONT_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_bind_memory2.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
}
device_extensions_.khr_bind_memory2 = functions_loaded;
}
if (device_extensions_.khr_get_memory_requirements2) {
bool functions_loaded = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 1, 0)) {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_get_memory_requirements2.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
} else {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_DONT_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_get_memory_requirements2.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
}
device_extensions_.khr_get_memory_requirements2 = functions_loaded;
// VK_KHR_dedicated_allocation can still work without the dedicated
// allocation preference getter even though it requires
// VK_KHR_get_memory_requirements2 to be supported and enabled.
}
if (device_extensions_.khr_maintenance4) {
bool functions_loaded = true;
if (device_properties_.apiVersion >= VK_MAKE_API_VERSION(0, 1, 3, 0)) {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_maintenance4.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
} else {
#define XE_UI_VULKAN_FUNCTION_PROMOTED XE_UI_VULKAN_FUNCTION_DONT_PROMOTE
#include "xenia/ui/vulkan/functions/device_khr_maintenance4.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
}
device_extensions_.khr_maintenance4 = functions_loaded;
}
if (device_extensions_.khr_swapchain) {
bool functions_loaded = true;
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
@ -956,12 +1008,20 @@ bool VulkanProvider::Initialize() {
XELOGVK("Vulkan device extensions:");
XELOGVK("* VK_EXT_fragment_shader_interlock: {}",
device_extensions_.ext_fragment_shader_interlock ? "yes" : "no");
XELOGVK("* VK_EXT_memory_budget: {}",
device_extensions_.ext_memory_budget ? "yes" : "no");
XELOGVK("* VK_EXT_shader_stencil_export: {}",
device_extensions_.ext_shader_stencil_export ? "yes" : "no");
XELOGVK("* VK_KHR_bind_memory2: {}",
device_extensions_.khr_bind_memory2 ? "yes" : "no");
XELOGVK("* VK_KHR_dedicated_allocation: {}",
device_extensions_.khr_dedicated_allocation ? "yes" : "no");
XELOGVK("* VK_KHR_get_memory_requirements2: {}",
device_extensions_.khr_get_memory_requirements2 ? "yes" : "no");
XELOGVK("* VK_KHR_image_format_list: {}",
device_extensions_.khr_image_format_list ? "yes" : "no");
XELOGVK("* VK_KHR_maintenance4: {}",
device_extensions_.khr_maintenance4 ? "yes" : "no");
XELOGVK("* VK_KHR_portability_subset: {}",
device_extensions_.khr_portability_subset ? "yes" : "no");
if (device_extensions_.khr_portability_subset) {

View file

@ -132,11 +132,18 @@ class VulkanProvider : public GraphicsProvider {
}
struct DeviceExtensions {
bool ext_fragment_shader_interlock;
bool ext_memory_budget;
bool ext_shader_stencil_export;
// Core since 1.1.0.
bool khr_bind_memory2;
// Core since 1.1.0.
bool khr_dedicated_allocation;
// Core since 1.1.0.
bool khr_get_memory_requirements2;
// Core since 1.2.0.
bool khr_image_format_list;
// Core since 1.3.0.
bool khr_maintenance4;
// Requires the VK_KHR_get_physical_device_properties2 instance extension.
bool khr_portability_subset;
// Core since 1.1.0.
@ -217,8 +224,14 @@ class VulkanProvider : public GraphicsProvider {
VkDevice device() const { return device_; }
struct DeviceFunctions {
#define XE_UI_VULKAN_FUNCTION(name) PFN_##name name;
#define XE_UI_VULKAN_FUNCTION_PROMOTED(extension_name, core_name) \
PFN_##extension_name extension_name;
#include "xenia/ui/vulkan/functions/device_1_0.inc"
#include "xenia/ui/vulkan/functions/device_khr_bind_memory2.inc"
#include "xenia/ui/vulkan/functions/device_khr_get_memory_requirements2.inc"
#include "xenia/ui/vulkan/functions/device_khr_maintenance4.inc"
#include "xenia/ui/vulkan/functions/device_khr_swapchain.inc"
#undef XE_UI_VULKAN_FUNCTION_PROMOTED
#undef XE_UI_VULKAN_FUNCTION
};
const DeviceFunctions& dfn() const { return dfn_; }

1
third_party/VulkanMemoryAllocator vendored Submodule

@ -0,0 +1 @@
Subproject commit 51c8b56011303e94840370089f816b19dbe7edf0