renderer_vulkan: Use VMA for buffers

This commit is contained in:
GPUCode 2023-05-27 17:09:17 +03:00
parent 48e39756f1
commit 7b2f680468
16 changed files with 262 additions and 211 deletions

View file

@ -603,6 +603,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
};
const VmaAllocatorCreateInfo allocator_info = {
.flags = VMA_ALLOCATOR_CREATE_EXTERNALLY_SYNCHRONIZED_BIT,
.physicalDevice = physical,
.device = *logical,
.pVulkanFunctions = &functions,

View file

@ -51,11 +51,59 @@ struct Range {
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
}
[[nodiscard]] VkMemoryPropertyFlags MemoryUsageRequiredVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
case MemoryUsage::Download:
return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return {};
}
[[nodiscard]] VkMemoryPropertyFlags MemoryUsagePreferedVmaFlags(MemoryUsage usage) {
return usage != MemoryUsage::DeviceLocal ? VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
: VkMemoryPropertyFlags{};
}
[[nodiscard]] VmaAllocationCreateFlags MemoryUsageVmaFlags(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::Upload:
case MemoryUsage::Stream:
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
case MemoryUsage::Download:
return VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT;
case MemoryUsage::DeviceLocal:
return VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT;
}
return {};
}
[[nodiscard]] VmaMemoryUsage MemoryUsageVma(MemoryUsage usage) {
switch (usage) {
case MemoryUsage::DeviceLocal:
case MemoryUsage::Stream:
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
}
return VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
}
} // Anonymous namespace
class MemoryAllocation {
@ -178,17 +226,18 @@ void MemoryCommit::Release() {
}
MemoryAllocator::MemoryAllocator(const Device& device_)
: device{device_}, properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
buffer_image_granularity{
device_.GetPhysical().GetProperties().limits.bufferImageGranularity} {}
MemoryAllocator::~MemoryAllocator() = default;
vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
const VmaAllocationCreateInfo alloc_info = {
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
.requiredFlags = 0,
.requiredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
.preferredFlags = 0,
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
@ -196,12 +245,40 @@ vk::Image MemoryAllocator::CreateImage(const VkImageCreateInfo& ci) const {
VkImage handle{};
VmaAllocation allocation{};
vk::Check(
vmaCreateImage(device.GetAllocator(), &ci, &alloc_info, &handle, &allocation, nullptr));
return vk::Image(handle, *device.GetLogical(), device.GetAllocator(), allocation,
vk::Check(vmaCreateImage(allocator, &ci, &alloc_ci, &handle, &allocation, nullptr));
return vk::Image(handle, *device.GetLogical(), allocator, allocation,
device.GetDispatchLoader());
}
vk::Buffer MemoryAllocator::CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const {
const VmaAllocationCreateInfo alloc_ci = {
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT | VMA_ALLOCATION_CREATE_MAPPED_BIT |
MemoryUsageVmaFlags(usage),
.usage = MemoryUsageVma(usage),
.requiredFlags = MemoryUsageRequiredVmaFlags(usage),
.preferredFlags = MemoryUsagePreferedVmaFlags(usage),
.pool = VK_NULL_HANDLE,
.pUserData = nullptr,
};
VkBuffer handle{};
VmaAllocationInfo alloc_info{};
VmaAllocation allocation{};
VkMemoryPropertyFlags property_flags{};
vk::Check(vmaCreateBuffer(allocator, &ci, &alloc_ci, &handle, &allocation, &alloc_info));
vmaGetAllocationMemoryProperties(allocator, allocation, &property_flags);
u8* data = reinterpret_cast<u8*>(alloc_info.pMappedData);
const std::span<u8> mapped_data = data ? std::span<u8>{data, ci.size} : std::span<u8>{};
const bool is_coherent = property_flags & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
return vk::Buffer(handle, *device.GetLogical(), allocator, allocation, mapped_data, is_coherent,
device.GetDispatchLoader());
}
MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, MemoryUsage usage) {
// Find the fastest memory flags we can afford with the current requirements
const u32 type_mask = requirements.memoryTypeBits;
@ -221,12 +298,6 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements& requirements, M
return TryCommit(requirements, flags).value();
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer& buffer, MemoryUsage usage) {
auto commit = Commit(device.GetLogical().GetBufferMemoryRequirements(*buffer), usage);
buffer.BindMemory(commit.Memory(), commit.Offset());
return commit;
}
bool MemoryAllocator::TryAllocMemory(VkMemoryPropertyFlags flags, u32 type_mask, u64 size) {
const u32 type = FindType(flags, type_mask).value();
vk::DeviceMemory memory = device.GetLogical().TryAllocateMemory({
@ -302,16 +373,4 @@ std::optional<u32> MemoryAllocator::FindType(VkMemoryPropertyFlags flags, u32 ty
return std::nullopt;
}
bool IsHostVisible(MemoryUsage usage) noexcept {
switch (usage) {
case MemoryUsage::DeviceLocal:
return false;
case MemoryUsage::Upload:
case MemoryUsage::Download:
return true;
}
ASSERT_MSG(false, "Invalid memory usage={}", usage);
return false;
}
} // namespace Vulkan

View file

@ -9,6 +9,8 @@
#include "common/common_types.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
VK_DEFINE_HANDLE(VmaAllocator)
namespace Vulkan {
class Device;
@ -17,9 +19,11 @@ class MemoryAllocation;
/// Hints and requirements for the backing memory type of a commit
enum class MemoryUsage {
DeviceLocal, ///< Hints device local usages, fastest memory type to read and write from the GPU
DeviceLocal, ///< Requests device local host visible buffer, falling back to device local
///< memory.
Upload, ///< Requires a host visible memory type optimized for CPU to GPU uploads
Download, ///< Requires a host visible memory type optimized for GPU to CPU readbacks
Stream, ///< Requests device local host visible buffer, falling back host memory.
};
/// Ownership handle of a memory commitment.
@ -82,6 +86,8 @@ public:
vk::Image CreateImage(const VkImageCreateInfo& ci) const;
vk::Buffer CreateBuffer(const VkBufferCreateInfo& ci, MemoryUsage usage) const;
/**
* Commits a memory with the specified requirements.
*
@ -113,13 +119,11 @@ private:
std::optional<u32> FindType(VkMemoryPropertyFlags flags, u32 type_mask) const;
const Device& device; ///< Device handle.
VmaAllocator allocator; ///< Vma allocator.
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device properties.
std::vector<std::unique_ptr<MemoryAllocation>> allocations; ///< Current allocations.
VkDeviceSize buffer_image_granularity; // The granularity for adjacent offsets between buffers
// and optimal images
};
/// Returns true when a memory usage is guaranteed to be host visible.
bool IsHostVisible(MemoryUsage usage) noexcept;
} // namespace Vulkan

View file

@ -561,14 +561,28 @@ void Image::Release() const noexcept {
}
}
void Buffer::BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const {
Check(dld->vkBindBufferMemory(owner, handle, memory, offset));
void Buffer::Flush() const {
if (!is_coherent) {
vmaFlushAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
}
}
void Buffer::Invalidate() const {
if (!is_coherent) {
vmaInvalidateAllocation(allocator, allocation, 0, VK_WHOLE_SIZE);
}
}
void Buffer::SetObjectNameEXT(const char* name) const {
SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER, name);
}
void Buffer::Release() const noexcept {
if (handle) {
vmaDestroyBuffer(allocator, handle, allocation);
}
}
void BufferView::SetObjectNameEXT(const char* name) const {
SetObjectName(dld, owner, handle, VK_OBJECT_TYPE_BUFFER_VIEW, name);
}
@ -707,12 +721,6 @@ Queue Device::GetQueue(u32 family_index) const noexcept {
return Queue(queue, *dld);
}
Buffer Device::CreateBuffer(const VkBufferCreateInfo& ci) const {
VkBuffer object;
Check(dld->vkCreateBuffer(handle, &ci, nullptr, &object));
return Buffer(object, handle, *dld);
}
BufferView Device::CreateBufferView(const VkBufferViewCreateInfo& ci) const {
VkBufferView object;
Check(dld->vkCreateBufferView(handle, &ci, nullptr, &object));

View file

@ -673,6 +673,84 @@ private:
const DeviceDispatch* dld = nullptr;
};
class Buffer {
public:
explicit Buffer(VkBuffer handle_, VkDevice owner_, VmaAllocator allocator_,
VmaAllocation allocation_, std::span<u8> mapped_, bool is_coherent_,
const DeviceDispatch& dld_) noexcept
: handle{handle_}, owner{owner_}, allocator{allocator_},
allocation{allocation_}, mapped{mapped_}, is_coherent{is_coherent_}, dld{&dld_} {}
Buffer() = default;
Buffer(const Buffer&) = delete;
Buffer& operator=(const Buffer&) = delete;
Buffer(Buffer&& rhs) noexcept
: handle{std::exchange(rhs.handle, nullptr)}, owner{rhs.owner}, allocator{rhs.allocator},
allocation{rhs.allocation}, mapped{rhs.mapped},
is_coherent{rhs.is_coherent}, dld{rhs.dld} {}
Buffer& operator=(Buffer&& rhs) noexcept {
Release();
handle = std::exchange(rhs.handle, nullptr);
owner = rhs.owner;
allocator = rhs.allocator;
allocation = rhs.allocation;
mapped = rhs.mapped;
is_coherent = rhs.is_coherent;
dld = rhs.dld;
return *this;
}
~Buffer() noexcept {
Release();
}
VkBuffer operator*() const noexcept {
return handle;
}
void reset() noexcept {
Release();
handle = nullptr;
}
explicit operator bool() const noexcept {
return handle != nullptr;
}
/// Returns the host mapped memory, an empty span otherwise.
std::span<u8> Mapped() noexcept {
return mapped;
}
std::span<const u8> Mapped() const noexcept {
return mapped;
}
/// Returns true if the buffer is mapped to the host.
bool IsHostVisible() const noexcept {
return !mapped.empty();
}
void Flush() const;
void Invalidate() const;
void SetObjectNameEXT(const char* name) const;
private:
void Release() const noexcept;
VkBuffer handle = nullptr;
VkDevice owner = nullptr;
VmaAllocator allocator = nullptr;
VmaAllocation allocation = nullptr;
std::span<u8> mapped = {};
bool is_coherent = false;
const DeviceDispatch* dld = nullptr;
};
class Queue {
public:
/// Construct an empty queue handle.
@ -696,17 +774,6 @@ private:
const DeviceDispatch* dld = nullptr;
};
class Buffer : public Handle<VkBuffer, VkDevice, DeviceDispatch> {
using Handle<VkBuffer, VkDevice, DeviceDispatch>::Handle;
public:
/// Attaches a memory allocation.
void BindMemory(VkDeviceMemory memory, VkDeviceSize offset) const;
/// Set object name.
void SetObjectNameEXT(const char* name) const;
};
class BufferView : public Handle<VkBufferView, VkDevice, DeviceDispatch> {
using Handle<VkBufferView, VkDevice, DeviceDispatch>::Handle;
@ -886,8 +953,6 @@ public:
Queue GetQueue(u32 family_index) const noexcept;
Buffer CreateBuffer(const VkBufferCreateInfo& ci) const;
BufferView CreateBufferView(const VkBufferViewCreateInfo& ci) const;
ImageView CreateImageView(const VkImageViewCreateInfo& ci) const;