init
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user