This commit is contained in:
xsl
2025-09-04 10:54:47 +08:00
commit 6bc8f61b18
1808 changed files with 208268 additions and 0 deletions
+633
View File
@@ -0,0 +1,633 @@
/* Copyright (c) 2021-2025, NVIDIA CORPORATION. All rights reserved.
* Copyright (c) 2024-2025, Bradley Austin Davis. All rights reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include "common/error.h"
#include "core/physical_device.h"
#include "core/vulkan_resource.h"
namespace vkb
{
namespace allocated
{
/**
* @brief Retrieves a reference to the VMA allocator singleton. It will hold an opaque handle to the VMA
* allocator between calls to `init` and `shutdown`. Otherwise it contains a null pointer.
* @return A reference to the VMA allocator singleton handle.
*/
VmaAllocator &get_memory_allocator();
/**
* @brief The non-templatized VMA initializer function, referenced by the template version to smooth
* over the differences between the `vkb::Device` and `vkb::core::HPPDevice` classes.
* Idempotent, but should be paired with `shutdown`.
* @param create_info The VMA allocator create info.
*/
void init(const VmaAllocatorCreateInfo &create_info);
/**
* @brief Initializes the VMA allocator with the specified device, expressed
* as the `vkb` wrapper class, which might be `vkb::Device` or `vkb::core::HPPDevice`.
* @tparam DeviceType The type of the device.
* @param device The Vulkan device.
*/
template <typename DeviceType = vkb::core::DeviceC>
void init(const DeviceType &device)
{
VmaVulkanFunctions vma_vulkan_func{};
vma_vulkan_func.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
vma_vulkan_func.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
VmaAllocatorCreateInfo allocator_info{};
allocator_info.pVulkanFunctions = &vma_vulkan_func;
allocator_info.physicalDevice = static_cast<VkPhysicalDevice>(device.get_gpu().get_handle());
allocator_info.device = static_cast<VkDevice>(device.get_handle());
allocator_info.instance = static_cast<VkInstance>(device.get_gpu().get_instance().get_handle());
bool can_get_memory_requirements = device.get_gpu().is_extension_supported(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME);
bool has_dedicated_allocation = device.get_gpu().is_extension_supported(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME);
if (can_get_memory_requirements && has_dedicated_allocation && device.is_extension_enabled(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_KHR_DEDICATED_ALLOCATION_BIT;
}
if (device.get_gpu().is_extension_supported(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) &&
device.is_extension_enabled(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT;
}
if (device.get_gpu().is_extension_supported(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME) && device.is_extension_enabled(VK_EXT_MEMORY_BUDGET_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
}
if (device.get_gpu().is_extension_supported(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) &&
device.is_extension_enabled(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT;
}
if (device.get_gpu().is_extension_supported(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME) && device.is_extension_enabled(VK_KHR_BIND_MEMORY_2_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_KHR_BIND_MEMORY2_BIT;
}
if (device.get_gpu().is_extension_supported(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME) &&
device.is_extension_enabled(VK_AMD_DEVICE_COHERENT_MEMORY_EXTENSION_NAME))
{
allocator_info.flags |= VMA_ALLOCATOR_CREATE_AMD_DEVICE_COHERENT_MEMORY_BIT;
}
init(allocator_info);
}
/**
* @brief Shuts down the VMA allocator and releases all resources. Should be preceeded with a call to `init`.
*/
void shutdown();
/**
* @brief The `Allocated` class serves as a base class for wrappers around Vulkan that require memory allocation
* (`VkImage` and `VkBuffer`). This class mostly ensures proper behavior for a RAII pattern, preventing double-release by
* preventing copy assignment and copy construction in favor of move semantics, as well as preventing default construction
* in favor of explicit construction with a pre-existing handle or a populated create info struct.
*
* This project uses the [VMA](https://gpuopen.com/vulkan-memory-allocator/) to handle the low
* level details of memory allocation and management, as it hides away many of the messyy details of
* memory allocation when a user is first learning Vulkan, but still allows for fine grained control
* when a user becomes more experienced and the situation calls for it.
*
* @note Constants used in this documentation in the form of `HOST_COHERENT` are shorthand for
* `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT` used for the sake of brevity.
*
* @tparam bindingType A flag indicating whether this is being used with the C or C++ API
*/
template <vkb::BindingType bindingType, typename HandleType>
class Allocated : public vkb::core::VulkanResource<bindingType, HandleType>
{
public:
using ParentType = vkb::core::VulkanResource<bindingType, HandleType>;
using BufferType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Buffer, VkBuffer>::type;
using BufferCreateInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::BufferCreateInfo, VkBufferCreateInfo>::type;
using DeviceMemoryType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceMemory, VkDeviceMemory>::type;
using DeviceSizeType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::DeviceSize, VkDeviceSize>::type;
using ImageCreateInfoType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::ImageCreateInfo, VkImageCreateInfo>::type;
using ImageType = typename std::conditional<bindingType == vkb::BindingType::Cpp, vk::Image, VkImage>::type;
public:
Allocated() = delete;
Allocated(const Allocated &) = delete;
Allocated(Allocated &&other) noexcept;
Allocated &operator=(Allocated const &other) = delete;
Allocated &operator=(Allocated &&other) = default;
protected:
/**
* @brief The VMA-specific constructor for new objects. This should only be visible to derived classes.
* @param allocation_create_info All of the non-resource-specific information needed by the VMA to allocate the memory.
* @param args Additional constructor arguments needed for the derived class. Typically a `VkImageCreateInfo` or `VkBufferCreateInfo` struct.
*/
template <typename... Args>
Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args);
/**
* @brief This constructor is used when the handle is already created, and the user wants to wrap it in an `Allocated` object.
* @note This constructor is used when the API provides us a pre-existing handle to something we didn't actually allocate, for instance
* when we allocate a swapchain and access the images in it. In these cases the `allocation` member variable will remain null for the
* lifetime of the wrapper object (which is NOT necessarily the lifetime of the handle) and the wrapper will make no attempt to apply
* RAII semantics.
*/
Allocated(HandleType handle, vkb::core::Device<bindingType> *device_ = nullptr);
public:
const HandleType *get() const;
/**
* @brief Flushes memory if it is NOT `HOST_COHERENT` (which also implies `HOST_VISIBLE`).
* This is a no-op for `HOST_COHERENT` memory.
*
* @param offset The offset into the memory to flush. Defaults to 0.
* @param size The size of the memory to flush. Defaults to the entire block of memory.
*/
void flush(DeviceSizeType offset = 0, DeviceSizeType size = VK_WHOLE_SIZE);
/**
* @brief Retrieves a pointer to the host visible memory as an unsigned byte array.
* @return The pointer to the host visible memory.
* @note This performs no checking that the memory is actually mapped, so it's possible to get a nullptr
*/
const uint8_t *get_data() const;
/**
* @brief Retrieves the raw Vulkan memory object.
* @return The Vulkan memory object.
*/
DeviceMemoryType get_memory() const;
/**
* @brief Maps Vulkan memory if it isn't already mapped to a host visible address. Does nothing if the
* allocation is already mapped (including persistently mapped allocations).
* @return Pointer to host visible memory.
*/
uint8_t *map();
/**
* @brief Returns true if the memory is mapped (i.e. the object contains a pointer for the mapping).
* This is true for both objects where `map` has been called as well as objects created with persistent
* mapping, where no call to `map` is necessary.
* @return mapping status.
*/
bool mapped() const;
/**
* @brief Unmaps Vulkan memory from the host visible address. Does nothing if the memory is not mapped or
* if the allocation is persistently mapped.
*/
void unmap();
/**
* @brief Copies the specified unsigned byte data into the mapped memory region.
* @note For non-persistently mapped memory, this function will call the `map` and `unmap` methods and SHOULD NOT
* be used if the user intends to make multiple updates to the memory region. In that case, the user should call
* `map` once, make all the updates against the pointer returned by `get_data`, and then call `unmap`. This may
* be a poor design choice as it creates a side effect of using the method (that mapped memory will
* unexpectedly be unmapped), but it is the current design of the method and changing it would be burdensome.
* Refactoring could be eased by creating a new method with a more explicit name, and then removing this method
* entirely.
*
* @param data The data to copy from.
* @param size The amount of bytes to copy.
* @param offset The offset to start the copying into the mapped data. Defaults to 0.
*/
size_t update(const uint8_t *data, size_t size, size_t offset = 0);
/**
* @brief Converts any non-byte data into bytes and then updates the buffer. This allows the user to pass
* arbitrary structure pointers to the update method, which will then be copied into the buffer as bytes.
* @param data The data to copy from.
* @param size The amount of bytes to copy.
* @param offset The offset to start the copying into the mapped data. Defaults to 0.
*/
size_t update(void const *data, size_t size, size_t offset = 0);
/**
* @brief Copies a vector of items into the buffer. This is a convenience method that allows the user to
* pass a vector of items to the update method, which will then be copied into the buffer as bytes.
*
* This function DOES NOT automatically manage adhering to the alignment requirements of the items being copied,
* for instance the `minUniformBufferOffsetAlignment` property of the [device](https://vulkan.gpuinfo.org/displaydevicelimit.php?name=minUniformBufferOffsetAlignment&platform=all).
* If the data needs to be aligned on something other than `sizeof(T)`, the user must manage that themselves.
* @param data The data vector to upload
* @param offset The offset to start the copying into the mapped data
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <typename T>
size_t update(std::vector<T> const &data, size_t offset = 0)
{
return update(data.data(), data.size() * sizeof(T), offset);
}
/**
* @brief Another convenience method, similar to the vector update method, but for std::array. The same caveats apply.
* @param data The data vector to upload
* @param offset The offset to start the copying into the mapped data
* @see update(std::vector<T> const &data, size_t offset = 0)
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <typename T, size_t N>
size_t update(std::array<T, N> const &data, size_t offset = 0)
{
return update(data.data(), data.size() * sizeof(T), offset);
}
/**
* @brief Copies an object as byte data into the buffer. This is a convenience method that allows the user to
* pass an object to the update method, which will then be copied into the buffer as bytes. The name difference
* is to avoid amibuity with the `update` method signatures (including the non-templated version)
* @param object The object to convert into byte data
* @param offset The offset to start the copying into the mapped data
* @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead.
*/
template <class T>
size_t convert_and_update(const T &object, size_t offset = 0)
{
return update(reinterpret_cast<const uint8_t *>(&object), sizeof(T), offset);
}
/**
* @brief Copies an object as byte data into the buffer. This is a convenience method that allows the user to
* pass an object to the update method, which will then be copied into the buffer as bytes. The use of the `vk::ArrayProxy`
* type here to wrap the passed data means you can use any type related to T that can be used as a constructor to `vk::ArrayProxy`.
* This includes `T`, `std::vector<T>`, `std::array<T, N>`, and `vk::ArrayProxy<T>`.
*
* @remark This was previously not feasible as it would have been undesirable to create a strong coupling with the
* C++ Vulkan bindings where the `vk::ArrayProxy` type is defined. However, structural changes have ensured that this
* coupling is always present, so the `vk::ArrayProxy` may as well be used to our advantage here.
*
* @note This function DOES NOT automatically manage adhering to the alignment requirements of the items being copied,
* for instance the `minUniformBufferOffsetAlignment` property of the [device](https://vulkan.gpuinfo.org/displaydevicelimit.php?name=minUniformBufferOffsetAlignment&platform=all).
* If the data needs to be aligned on something other than `sizeof(T)`, the user must manage that themselves.
*
* @todo create `updateTypedAligned` which has an additional argument specifying the required GPU alignment of the elements of the array.
*/
template <class T>
size_t updateTyped(const vk::ArrayProxy<T> &object, size_t offset = 0)
{
return update(reinterpret_cast<const uint8_t *>(object.data()), object.size() * sizeof(T), offset);
}
protected:
/**
* @brief Internal method to actually create the buffer, allocate the memory and bind them.
* Should only be called from the `Buffer` derived class.
*
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
[[nodiscard]] BufferType create_buffer(BufferCreateInfoType const &create_info);
/**
* @brief Internal method to actually create the image, allocate the memory and bind them.
* Should only be called from the `Image` derived class.
*
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
[[nodiscard]] ImageType create_image(ImageCreateInfoType const &create_info);
/**
* @brief The post_create method is called after the creation of a buffer or image to store the allocation info internally. Derived classes
* could in theory override this to ensure any post-allocation operations are performed, but the base class should always be called to ensure
* the allocation info is stored.
* Should only be called in the corresponding `create_xxx` methods.
*/
virtual void post_create(VmaAllocationInfo const &allocation_info);
/**
* @brief Internal method to actually destroy the buffer and release the allocated memory. Should
* only be called from the `Buffer` derived class.
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
void destroy_buffer(BufferType buffer);
/**
* @brief Internal method to actually destroy the image and release the allocated memory. Should
* only be called from the `Image` derived class.
* Present in this common base class in order to allow the internal state members to remain `private`
* instead of `protected`, and because it (mostly) isolates interaction with the VMA to a single class
*/
void destroy_image(ImageType image);
/**
* @brief Clears the internal state. Can be overridden by derived classes to perform additional cleanup of members.
* Should only be called in the corresping `destroy_xxx` methods.
*/
void clear();
private:
vk::Buffer create_buffer_impl(vk::BufferCreateInfo const &create_info);
vk::Image create_image_impl(vk::ImageCreateInfo const &create_info);
VmaAllocationCreateInfo allocation_create_info = {};
VmaAllocation allocation = VK_NULL_HANDLE;
/**
* @brief A pointer to the allocation memory, if the memory is HOST_VISIBLE and is currently (or persistently) mapped.
* Contains null otherwise.
*/
uint8_t *mapped_data = nullptr;
/**
* @brief This flag is set to true if the memory is coherent and doesn't need to be flushed after writes.
*
* @note This is initialized at allocation time to avoid subsequent need to call a function to fetch the
* allocation information from the VMA, since this property won't change for the lifetime of the allocation.
*/
bool coherent = false;
/**
* @brief This flag is set to true if the memory is persistently mapped (i.e. not just HOST_VISIBLE, but available
* as a pointer to the application for the lifetime of the allocation).
*
* @note This is initialized at allocation time to avoid subsequent need to call a function to fetch the
* allocation information from the VMA, since this property won't change for the lifetime of the allocation.
*/
bool persistent = false;
};
template <vkb::BindingType bindingType, typename HandleType>
inline Allocated<bindingType, HandleType>::Allocated(Allocated &&other) noexcept :
ParentType{static_cast<ParentType &&>(other)},
allocation_create_info(std::exchange(other.allocation_create_info, {})),
allocation(std::exchange(other.allocation, {})),
mapped_data(std::exchange(other.mapped_data, {})),
coherent(std::exchange(other.coherent, {})),
persistent(std::exchange(other.persistent, {}))
{
}
template <vkb::BindingType bindingType, typename HandleType>
template <typename... Args>
inline Allocated<bindingType, HandleType>::Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args) :
ParentType{std::forward<Args>(args)...},
allocation_create_info(allocation_create_info)
{}
template <vkb::BindingType bindingType, typename HandleType>
inline Allocated<bindingType, HandleType>::Allocated(HandleType handle, vkb::core::Device<bindingType> *device_) :
ParentType(handle, device_)
{}
template <vkb::BindingType bindingType, typename HandleType>
inline const HandleType *Allocated<bindingType, HandleType>::get() const
{
return &ParentType::get_handle();
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::clear()
{
mapped_data = nullptr;
persistent = false;
allocation_create_info = {};
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::BufferType Allocated<bindingType, HandleType>::create_buffer(BufferCreateInfoType const &create_info)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_buffer_impl(create_info);
}
else
{
return static_cast<VkBuffer>(create_buffer_impl(reinterpret_cast<vk::BufferCreateInfo const &>(create_info)));
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline vk::Buffer Allocated<bindingType, HandleType>::create_buffer_impl(vk::BufferCreateInfo const &create_info)
{
vk::Buffer buffer = VK_NULL_HANDLE;
VmaAllocationInfo allocation_info{};
auto result = vmaCreateBuffer(
get_memory_allocator(),
reinterpret_cast<VkBufferCreateInfo const *>(&create_info),
&allocation_create_info,
reinterpret_cast<VkBuffer *>(&buffer),
&allocation,
&allocation_info);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Buffer"};
}
post_create(allocation_info);
return buffer;
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::ImageType Allocated<bindingType, HandleType>::create_image(ImageCreateInfoType const &create_info)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return create_image_impl(create_info);
}
else
{
return static_cast<VkImage>(create_image_impl(reinterpret_cast<vk::ImageCreateInfo const &>(create_info)));
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline vk::Image Allocated<bindingType, HandleType>::create_image_impl(vk::ImageCreateInfo const &create_info)
{
assert(0 < create_info.mipLevels && "Images should have at least one level");
assert(0 < create_info.arrayLayers && "Images should have at least one layer");
assert(create_info.usage && "Images should have at least one usage type");
vk::Image image = VK_NULL_HANDLE;
VmaAllocationInfo allocation_info{};
#if 0
// If the image is an attachment, prefer dedicated memory
constexpr vk::ImageUsageFlags attachment_only_flags = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eTransientAttachment;
if (create_info.usage & attachment_only_flags)
{
allocation_create_info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
}
if (create_info.usage & vk::ImageUsageFlagBits::eTransientAttachment)
{
allocation_create_info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT;
}
#endif
VkResult result = vmaCreateImage(get_memory_allocator(),
reinterpret_cast<VkImageCreateInfo const *>(&create_info),
&allocation_create_info,
reinterpret_cast<VkImage *>(&image),
&allocation,
&allocation_info);
if (result != VK_SUCCESS)
{
throw VulkanException{result, "Cannot create Image"};
}
post_create(allocation_info);
return image;
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::destroy_buffer(BufferType handle)
{
if (handle != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE)
{
unmap();
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaDestroyBuffer(get_memory_allocator(), static_cast<VkBuffer>(handle), allocation);
}
else
{
vmaDestroyBuffer(get_memory_allocator(), handle, allocation);
}
clear();
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::destroy_image(ImageType image)
{
if (image != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE)
{
unmap();
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaDestroyImage(get_memory_allocator(), static_cast<VkImage>(image), allocation);
}
else
{
vmaDestroyImage(get_memory_allocator(), image, allocation);
}
clear();
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::flush(DeviceSizeType offset, DeviceSizeType size)
{
if (!coherent)
{
if constexpr (bindingType == vkb::BindingType::Cpp)
{
vmaFlushAllocation(get_memory_allocator(), allocation, static_cast<VkDeviceSize>(offset), static_cast<VkDeviceSize>(size));
}
else
{
vmaFlushAllocation(get_memory_allocator(), allocation, offset, size);
}
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline const uint8_t *Allocated<bindingType, HandleType>::get_data() const
{
return mapped_data;
}
template <vkb::BindingType bindingType, typename HandleType>
inline typename Allocated<bindingType, HandleType>::DeviceMemoryType Allocated<bindingType, HandleType>::get_memory() const
{
VmaAllocationInfo alloc_info;
vmaGetAllocationInfo(get_memory_allocator(), allocation, &alloc_info);
if constexpr (bindingType == vkb::BindingType::Cpp)
{
return static_cast<vk::DeviceMemory>(alloc_info.deviceMemory);
}
else
{
return alloc_info.deviceMemory;
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline uint8_t *Allocated<bindingType, HandleType>::map()
{
if (!persistent && !mapped())
{
VK_CHECK(vmaMapMemory(get_memory_allocator(), allocation, reinterpret_cast<void **>(&mapped_data)));
assert(mapped_data);
}
return mapped_data;
}
template <vkb::BindingType bindingType, typename HandleType>
inline bool Allocated<bindingType, HandleType>::mapped() const
{
return mapped_data != nullptr;
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::post_create(VmaAllocationInfo const &allocation_info)
{
VkMemoryPropertyFlags memory_properties;
vmaGetAllocationMemoryProperties(get_memory_allocator(), allocation, &memory_properties);
coherent = (memory_properties & VK_MEMORY_PROPERTY_HOST_COHERENT_BIT) == VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
mapped_data = static_cast<uint8_t *>(allocation_info.pMappedData);
persistent = mapped();
}
template <vkb::BindingType bindingType, typename HandleType>
inline void Allocated<bindingType, HandleType>::unmap()
{
if (!persistent && mapped())
{
vmaUnmapMemory(get_memory_allocator(), allocation);
mapped_data = nullptr;
}
}
template <vkb::BindingType bindingType, typename HandleType>
inline size_t Allocated<bindingType, HandleType>::update(const uint8_t *data, size_t size, size_t offset)
{
if (persistent)
{
std::copy(data, data + size, mapped_data + offset);
flush();
}
else
{
map();
std::copy(data, data + size, mapped_data + offset);
flush();
unmap();
}
return size;
}
template <vkb::BindingType bindingType, typename HandleType>
inline size_t Allocated<bindingType, HandleType>::update(void const *data, size_t size, size_t offset)
{
return update(reinterpret_cast<const uint8_t *>(data), size, offset);
}
template <typename HandleType>
using AllocatedC = Allocated<vkb::BindingType::C, HandleType>;
template <typename HandleType>
using AllocatedCpp = Allocated<vkb::BindingType::Cpp, HandleType>;
} // namespace allocated
} // namespace vkb