diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt index 3755930607..cf6ab72124 100644 --- a/framework/CMakeLists.txt +++ b/framework/CMakeLists.txt @@ -251,7 +251,6 @@ set(CORE_FILES core/render_pass.h core/query_pool.h core/acceleration_structure.h - core/hpp_allocated.h core/hpp_command_buffer.h core/hpp_command_pool.h core/hpp_debug.h diff --git a/framework/builder_base.h b/framework/builder_base.h index b70cb7b1db..7de6e47b06 100644 --- a/framework/builder_base.h +++ b/framework/builder_base.h @@ -24,6 +24,54 @@ namespace vkb { + +namespace allocated +{ + +/** + * @brief Many Vulkan resource types (most notably Images and to a lesser extent Buffers) + * and and their corresponding memory allocations have many parameters that need to be setup + * when creating them. Although many of these have reasonable defaults, constructors with + * numerous arguments, some or all of which may have default arguments, aren't well suited + * to partial customization. This is a common failing of languages that don't support named + * arguments and has led to the common use of the [builder pattern](https://en.wikipedia.org/wiki/Builder_pattern), + * where a helper class is used to store all the options that can be tweaked for an object + * when it's created. A builder class will have reasonable defaults where appropriate and only + * require arguments for the builder constructor when a value is always required for creation to occur + * (for example, the size of a buffer or the extent of an image). Remaining parameters can be set + * with methods on the builder class, which return a reference to the builder object, allowing + * chaining of the method calls. + * + * This builder class serves as a base containing options that are common to all + * [VMA](https://gpuopen.com/vulkan-memory-allocator/) allocated and managed resources. + * For instance, the VMA create and usage flags are set here, but the image or buffer + * usage flags are handled in the derived builder classes specific to those types. + * + * The following is an example of how the builder pattern is used in the codebase: + ```cpp + vkb::core::ImageBuilder(VkExtent3D{grid_width, grid_height, 1}) + .with_format(VK_FORMAT_R8G8B8A8_UNORM) + .with_usage(VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT) + .with_vma_usage(VMA_MEMORY_USAGE_GPU_ONLY) + .with_sample_count(VK_SAMPLE_COUNT_1_BIT) + .with_mip_levels(1) + .with_array_layers(1) + .with_tiling(VK_IMAGE_TILING_OPTIMAL) + .with_queue_families(static_cast(queue_families.size()), queue_families.data()) + .with_sharing_mode(sharing_mode)); + ``` + * The actual image can be created with `build()` which returns a `vkb::core::Image` or `buildPtr` which returns a `std::unique_ptr`. + * Alternatively, the builder can be used as an argument to the `Image` constructor, which will build the image for you in place. + * @note The builder pattern is intended to displace the currently used `vkb::core::Image` and `vkb::core::Buffer` constructors with numerous + * arguments, but this is a work in progress and not currently in wide use in the codebase. + * + * @tparam BuilderType Allow the same builder base class to be used + * with a variety of subclasses while using casting to return the corect dervied type + * from the modifier methods. + * @tparam bindingType A flag indicating whether this is being used with the C or C++ API + * @tparam CreateInfoType The type of the Vulkan create info structure. Either a `VkSomethingCreateInfo` + * or `vk::SomethingCreateInfo` for the C or C++ API respectively. + */ template class BuilderBase { @@ -211,4 +259,5 @@ inline BuilderType &BuilderBase::with_ return *static_cast(this); } +} // namespace allocated } // namespace vkb diff --git a/framework/core/allocated.cpp b/framework/core/allocated.cpp index eafa1e9552..28fd972788 100644 --- a/framework/core/allocated.cpp +++ b/framework/core/allocated.cpp @@ -17,6 +17,7 @@ */ #include "allocated.h" +#include "common/error.h" namespace vkb { @@ -32,10 +33,14 @@ VmaAllocator &get_memory_allocator() void init(const VmaAllocatorCreateInfo &create_info) { - VkResult result = vmaCreateAllocator(&create_info, &get_memory_allocator()); - if (result != VK_SUCCESS) + auto &allocator = get_memory_allocator(); + if (allocator == VK_NULL_HANDLE) { - throw VulkanException{result, "Cannot create allocator"}; + VkResult result = vmaCreateAllocator(&create_info, &allocator); + if (result != VK_SUCCESS) + { + throw VulkanException{result, "Cannot create allocator"}; + } } } @@ -52,182 +57,5 @@ void shutdown() } } -AllocatedBase::AllocatedBase(const VmaAllocationCreateInfo &alloc_create_info) : - alloc_create_info(alloc_create_info) -{ -} - -AllocatedBase::AllocatedBase(AllocatedBase &&other) noexcept : - alloc_create_info(std::exchange(other.alloc_create_info, {})), - allocation(std::exchange(other.allocation, {})), - mapped_data(std::exchange(other.mapped_data, {})), - coherent(std::exchange(other.coherent, {})), - persistent(std::exchange(other.persistent, {})) -{ -} - -const uint8_t *AllocatedBase::get_data() const -{ - return mapped_data; -} - -VkDeviceMemory AllocatedBase::get_memory() const -{ - VmaAllocationInfo alloc_info; - vmaGetAllocationInfo(get_memory_allocator(), allocation, &alloc_info); - return alloc_info.deviceMemory; -} - -void AllocatedBase::flush(VkDeviceSize offset, VkDeviceSize size) -{ - if (!coherent) - { - vmaFlushAllocation(get_memory_allocator(), allocation, offset, size); - } -} - -uint8_t *AllocatedBase::map() -{ - if (!persistent && !mapped()) - { - VK_CHECK(vmaMapMemory(get_memory_allocator(), allocation, reinterpret_cast(&mapped_data))); - assert(mapped_data); - } - return mapped_data; -} - -void AllocatedBase::unmap() -{ - if (!persistent && mapped()) - { - vmaUnmapMemory(get_memory_allocator(), allocation); - mapped_data = nullptr; - } -} - -size_t AllocatedBase::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; -} - -size_t AllocatedBase::update(void const *data, size_t size, size_t offset) -{ - return update(reinterpret_cast(data), size, offset); -} - -void AllocatedBase::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(allocation_info.pMappedData); - persistent = mapped(); -} - -[[nodiscard]] VkBuffer AllocatedBase::create_buffer(VkBufferCreateInfo const &create_info) -{ - VkBuffer handleResult = VK_NULL_HANDLE; - VmaAllocationInfo allocation_info{}; - - auto result = vmaCreateBuffer( - get_memory_allocator(), - &create_info, - &alloc_create_info, - &handleResult, - &allocation, - &allocation_info); - - if (result != VK_SUCCESS) - { - throw VulkanException{result, "Cannot create Buffer"}; - } - post_create(allocation_info); - return handleResult; -} - -[[nodiscard]] VkImage AllocatedBase::create_image(VkImageCreateInfo 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(0 < create_info.usage && "Images should have at least one usage type"); - - VkImage handleResult = VK_NULL_HANDLE; - VmaAllocationInfo allocation_info{}; - -#if 0 - // If the image is an attachment, prefer dedicated memory - constexpr VkImageUsageFlags attachment_only_flags = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; - if (create_info.usage & attachment_only_flags) - { - alloc_create_info.flags |= VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT; - } - - if (create_info.usage & VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT) - { - alloc_create_info.preferredFlags |= VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT; - } -#endif - - auto result = vmaCreateImage( - get_memory_allocator(), - &create_info, - &alloc_create_info, - &handleResult, - &allocation, - &allocation_info); - - if (result != VK_SUCCESS) - { - throw VulkanException{result, "Cannot create Image"}; - } - - post_create(allocation_info); - return handleResult; -} - -void AllocatedBase::destroy_buffer(VkBuffer handle) -{ - if (handle != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE) - { - unmap(); - vmaDestroyBuffer(get_memory_allocator(), handle, allocation); - clear(); - } -} - -void AllocatedBase::destroy_image(VkImage image) -{ - if (image != VK_NULL_HANDLE && allocation != VK_NULL_HANDLE) - { - unmap(); - vmaDestroyImage(get_memory_allocator(), image, allocation); - clear(); - } -} - -bool AllocatedBase::mapped() const -{ - return mapped_data != nullptr; -} - -void AllocatedBase::clear() -{ - mapped_data = nullptr; - persistent = false; - alloc_create_info = {}; -} - } // namespace allocated } // namespace vkb diff --git a/framework/core/allocated.h b/framework/core/allocated.h index 29bf14e745..c1a6751890 100644 --- a/framework/core/allocated.h +++ b/framework/core/allocated.h @@ -20,8 +20,6 @@ #include "common/error.h" #include "core/vulkan_resource.h" -#include -#include namespace vkb { @@ -30,8 +28,27 @@ class Device; 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 void init(const DeviceType &device) { @@ -80,66 +97,145 @@ void init(const DeviceType &device) init(allocator_info); } -VmaAllocator &get_memory_allocator(); - +/** + * @brief Shuts down the VMA allocator and releases all resources. Should be preceeded with a call to `init`. + */ void shutdown(); -class AllocatedBase +/** + * @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 +class Allocated : public vkb::core::VulkanResource { public: - AllocatedBase() = default; - AllocatedBase(const VmaAllocationCreateInfo &alloc_create_info); - AllocatedBase(AllocatedBase const &) = delete; - AllocatedBase(AllocatedBase &&other) noexcept; + using BufferType = typename std::conditional::type; + using BufferCreateInfoType = typename std::conditional::type; + using DeviceMemoryType = typename std::conditional::type; + using DeviceSizeType = typename std::conditional::type; + using ImageCreateInfoType = typename std::conditional::type; + using ImageType = typename std::conditional::type; + using DeviceType = typename std::conditional::type; + using ParentType = vkb::core::VulkanResource; - AllocatedBase &operator=(const AllocatedBase &) = delete; - AllocatedBase &operator=(AllocatedBase &&) = default; + public: + Allocated() = delete; + Allocated(const Allocated &) = delete; + Allocated(Allocated &&other) noexcept; + Allocated &operator=(Allocated const &other) = delete; + Allocated &operator=(Allocated &&other) = default; - const uint8_t *get_data() const; - VkDeviceMemory get_memory() const; + 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 + Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args); /** - * @brief Flushes memory if it is HOST_VISIBLE and not HOST_COHERENT + * @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. */ - void flush(VkDeviceSize offset = 0, VkDeviceSize size = VK_WHOLE_SIZE); + Allocated(HandleType handle, DeviceType *device_ = nullptr); + + public: + const HandleType *get() const; /** - * @brief Returns true if the memory is mapped, false otherwise - * @return mapping status + * @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. */ - bool mapped() const; + 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 an host visible address - * @return Pointer to host visible memory + * @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 Unmaps vulkan memory from the host visible address + * @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 byte data into the buffer - * @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 + * @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 - * @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 + * @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); /** - * @todo Use the vk::ArrayBuffer class to collapse some of these templates - * @brief Copies a vector of items into the buffer + * @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 size_t update(std::vector const &data, size_t offset = 0) @@ -147,6 +243,13 @@ class AllocatedBase 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 const &data, size_t offset = 0) + * @deprecated Use the `updateTyped` method that uses the `vk::ArrayProxy` class instead. + */ template size_t update(std::array const &data, size_t offset = 0) { @@ -154,65 +257,376 @@ class AllocatedBase } /** - * @brief Copies an object as byte data into the buffer + * @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 size_t convert_and_update(const T &object, size_t offset = 0) { return update(reinterpret_cast(&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`, `std::array`, and `vk::ArrayProxy`. + * + * @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 + size_t updateTyped(const vk::ArrayProxy &object, size_t offset = 0) + { + return update(reinterpret_cast(object.data()), object.size() * sizeof(T), offset); + } protected: - virtual void post_create(VmaAllocationInfo const &allocation_info); - [[nodiscard]] VkBuffer create_buffer(VkBufferCreateInfo const &create_info); - [[nodiscard]] VkImage create_image(VkImageCreateInfo const &create_info); - void destroy_buffer(VkBuffer buffer); - void destroy_image(VkImage image); - void clear(); - - VmaAllocationCreateInfo alloc_create_info{}; - VmaAllocation allocation = VK_NULL_HANDLE; - uint8_t *mapped_data = nullptr; - bool coherent = false; - bool persistent = false; // Whether the buffer is persistently mapped or not + /** + * @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 < - typename HandleType, - typename MemoryType = VkDeviceMemory, - typename ParentType = vkb::core::VulkanResourceC> -class Allocated : public ParentType, public AllocatedBase +template +inline Allocated::Allocated(Allocated &&other) noexcept : + ParentType{static_cast(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, {})) { - public: - using ParentType::ParentType; +} - Allocated() = delete; - Allocated(const Allocated &) = delete; - Allocated &operator=(Allocated const &other) = delete; - Allocated &operator=(Allocated &&other) = default; +template +template +inline Allocated::Allocated(const VmaAllocationCreateInfo &allocation_create_info, Args &&...args) : + ParentType{std::forward(args)...}, + allocation_create_info(allocation_create_info) +{} - // Import the base class constructors - template - Allocated(const VmaAllocationCreateInfo &alloc_create_info, Args &&...args) : - ParentType(std::forward(args)...), - AllocatedBase(alloc_create_info) +template +inline Allocated::Allocated(HandleType handle, DeviceType *device_) : + ParentType(handle, device_) +{} + +template +inline const HandleType *Allocated::get() const +{ + return &ParentType::get_handle(); +} + +template +inline void Allocated::clear() +{ + mapped_data = nullptr; + persistent = false; + allocation_create_info = {}; +} + +template +inline typename Allocated::BufferType Allocated::create_buffer(BufferCreateInfoType const &create_info) +{ + if constexpr (bindingType == vkb::BindingType::Cpp) + { + return create_buffer_impl(create_info); + } + else { + return static_cast(create_buffer_impl(reinterpret_cast(create_info))); } +} - Allocated(Allocated &&other) noexcept - : - ParentType{static_cast(other)}, - AllocatedBase{static_cast(other)} +template +inline vk::Buffer Allocated::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(&create_info), + &allocation_create_info, + reinterpret_cast(&buffer), + &allocation, + &allocation_info); + + if (result != VK_SUCCESS) { + throw VulkanException{result, "Cannot create Buffer"}; } + post_create(allocation_info); + return buffer; +} - const HandleType *get() const +template +inline typename Allocated::ImageType Allocated::create_image(ImageCreateInfoType const &create_info) +{ + if constexpr (bindingType == vkb::BindingType::Cpp) { - return &ParentType::get_handle(); + return create_image_impl(create_info); } -}; + else + { + return static_cast(create_image_impl(reinterpret_cast(create_info))); + } +} + +template +inline vk::Image Allocated::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(&create_info), + &allocation_create_info, + reinterpret_cast(&image), + &allocation, + &allocation_info); + + if (result != VK_SUCCESS) + { + throw VulkanException{result, "Cannot create Image"}; + } + + post_create(allocation_info); + return image; +} + +template +inline void Allocated::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(handle), allocation); + } + else + { + vmaDestroyBuffer(get_memory_allocator(), handle, allocation); + } + clear(); + } +} + +template +inline void Allocated::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(image), allocation); + } + else + { + vmaDestroyImage(get_memory_allocator(), image, allocation); + } + clear(); + } +} + +template +inline void Allocated::flush(DeviceSizeType offset, DeviceSizeType size) +{ + if (!coherent) + { + if constexpr (bindingType == vkb::BindingType::Cpp) + { + vmaFlushAllocation(get_memory_allocator(), allocation, static_cast(offset), static_cast(size)); + } + else + { + vmaFlushAllocation(get_memory_allocator(), allocation, offset, size); + } + } +} + +template +inline const uint8_t *Allocated::get_data() const +{ + return mapped_data; +} + +template +inline typename Allocated::DeviceMemoryType Allocated::get_memory() const +{ + VmaAllocationInfo alloc_info; + vmaGetAllocationInfo(get_memory_allocator(), allocation, &alloc_info); + if constexpr (bindingType == vkb::BindingType::Cpp) + { + return static_cast(alloc_info.deviceMemory); + } + else + { + return alloc_info.deviceMemory; + } +} + +template +inline uint8_t *Allocated::map() +{ + if (!persistent && !mapped()) + { + VK_CHECK(vmaMapMemory(get_memory_allocator(), allocation, reinterpret_cast(&mapped_data))); + assert(mapped_data); + } + return mapped_data; +} + +template +inline bool Allocated::mapped() const +{ + return mapped_data != nullptr; +} + +template +inline void Allocated::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(allocation_info.pMappedData); + persistent = mapped(); +} + +template +inline void Allocated::unmap() +{ + if (!persistent && mapped()) + { + vmaUnmapMemory(get_memory_allocator(), allocation); + mapped_data = nullptr; + } +} + +template +inline size_t Allocated::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 +inline size_t Allocated::update(void const *data, size_t size, size_t offset) +{ + return update(reinterpret_cast(data), size, offset); +} + +template +using AllocatedC = Allocated; +template +using AllocatedCpp = Allocated; } // namespace allocated } // namespace vkb diff --git a/framework/core/buffer.h b/framework/core/buffer.h index 211ed16d58..0a6282be1e 100644 --- a/framework/core/buffer.h +++ b/framework/core/buffer.h @@ -21,7 +21,6 @@ #include "builder_base.h" #include "common/vk_common.h" #include "core/allocated.h" -#include "core/hpp_allocated.h" namespace vkb { @@ -34,9 +33,9 @@ using BufferPtr = std::unique_ptr>; template struct BufferBuilder - : public vkb::BuilderBase, - typename std::conditional::type> + : public vkb::allocated::BuilderBase, + typename std::conditional::type> { public: using BufferCreateFlagsType = typename std::conditional::type; @@ -48,7 +47,7 @@ struct BufferBuilder using DeviceType = typename std::conditional::type; private: - using Parent = vkb::BuilderBase, BufferCreateInfoType>; + using ParentType = vkb::allocated::BuilderBase, BufferCreateInfoType>; public: BufferBuilder(DeviceSizeType size); @@ -64,13 +63,13 @@ using BufferBuilderCpp = BufferBuilder; template <> inline BufferBuilder::BufferBuilder(vk::DeviceSize size) : - Parent(BufferCreateInfoType{{}, size}) + ParentType(BufferCreateInfoType{{}, size}) { } template <> inline BufferBuilder::BufferBuilder(VkDeviceSize size) : - Parent(VkBufferCreateInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, 0, size}) + ParentType(VkBufferCreateInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, nullptr, 0, size}) {} template @@ -103,18 +102,17 @@ inline BufferBuilder &BufferBuilder::with_usage(Buffer template class Buffer - : public std::conditional, allocated::Allocated>::type + : public vkb::allocated::Allocated::type> { public: + using BufferType = typename std::conditional::type; using BufferUsageFlagsType = typename std::conditional::type; using DeviceSizeType = typename std::conditional::type; - using AllocatedType = - typename std::conditional, allocated::Allocated>::type; using DeviceType = typename std::conditional::type; private: - using Parent = AllocatedType; + using ParentType = vkb::allocated::Allocated; public: static Buffer create_staging_buffer(DeviceType &device, DeviceSizeType size, const void *data); @@ -232,7 +230,7 @@ inline Buffer::Buffer(DeviceType &device, template inline Buffer::Buffer(DeviceType &device, const BufferBuilder &builder) : - AllocatedType{builder.get_allocation_create_info(), nullptr, &device}, size(builder.get_create_info().size) + ParentType(builder.get_allocation_create_info(), nullptr, &device), size(builder.get_create_info().size) { this->set_handle(this->create_buffer(builder.get_create_info())); if (!builder.get_debug_name().empty()) diff --git a/framework/core/hpp_allocated.h b/framework/core/hpp_allocated.h deleted file mode 100644 index ddec31938d..0000000000 --- a/framework/core/hpp_allocated.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2021-2024, NVIDIA CORPORATION. All rights reserved. - * Copyright (c) 2024, 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 "allocated.h" -#include "vulkan_resource.h" - -namespace vkb -{ -namespace allocated -{ -template -class HPPAllocated : public Allocated< - HandleType, - vk::DeviceMemory, - vkb::core::VulkanResourceCpp> -{ - using Parent = Allocated>; - - public: - using Parent::get_handle; - using Parent::Parent; - using Parent::update; - - HPPAllocated() = delete; - HPPAllocated(HPPAllocated const &) = delete; - HPPAllocated(HPPAllocated &&rhs) = default; - HPPAllocated &operator=(HPPAllocated const &) = delete; - HPPAllocated &operator=(HPPAllocated &&rhs) = default; - - // Import the base class constructors - template - HPPAllocated(const VmaAllocationCreateInfo &alloc_create_info, Args &&...args) : - Parent(alloc_create_info, std::forward(args)...) - {} - - /** - * @brief Copies byte data into the buffer - * @param data The data to copy from - * @param offset The offset to start the copying into the mapped data - */ - template - vk::DeviceSize update(const vk::ArrayProxy &data, size_t offset = 0) - { - return Parent::update(static_cast(data.data()), data.size() * sizeof(T), offset); - } - - /** - * @brief Copies byte data into the buffer - * @param data The data to copy from - * @param count The number of array elements - * @param offset The offset to start the copying into the mapped data - */ - template - vk::DeviceSize update_from_array(const T *data, size_t count, size_t offset = 0) - { - return update(vk::ArrayProxy{data, count}, offset); - } -}; - -} // namespace allocated -} // namespace vkb diff --git a/framework/core/hpp_image.h b/framework/core/hpp_image.h index 9a780e44b5..39e0f12933 100644 --- a/framework/core/hpp_image.h +++ b/framework/core/hpp_image.h @@ -18,8 +18,8 @@ #pragma once #include "builder_base.h" +#include "core/allocated.h" #include "core/vulkan_resource.h" -#include "hpp_allocated.h" #include namespace vkb @@ -31,10 +31,10 @@ class HPPImageView; class HPPImage; using HPPImagePtr = std::unique_ptr; -struct HPPImageBuilder : public vkb::BuilderBaseCpp +struct HPPImageBuilder : public vkb::allocated::BuilderBaseCpp { private: - using Parent = vkb::BuilderBaseCpp; + using Parent = vkb::allocated::BuilderBaseCpp; public: HPPImageBuilder(vk::Extent3D const &extent) : @@ -105,7 +105,7 @@ struct HPPImageBuilder : public vkb::BuilderBaseCpp +class HPPImage : public vkb::allocated::AllocatedCpp { public: HPPImage(HPPDevice &device, diff --git a/framework/core/hpp_image_core.cpp b/framework/core/hpp_image_core.cpp index b8935abc5b..17ff7aaddd 100644 --- a/framework/core/hpp_image_core.cpp +++ b/framework/core/hpp_image_core.cpp @@ -79,7 +79,7 @@ HPPImage::HPPImage(HPPDevice &device, {} HPPImage::HPPImage(HPPDevice &device, HPPImageBuilder const &builder) : - HPPAllocated{builder.get_allocation_create_info(), nullptr, &device}, create_info{builder.get_create_info()} + vkb::allocated::AllocatedCpp{builder.get_allocation_create_info(), nullptr, &device}, create_info{builder.get_create_info()} { get_handle() = create_image(create_info.operator const VkImageCreateInfo &()); subresource.arrayLayer = create_info.arrayLayers; @@ -96,7 +96,7 @@ HPPImage::HPPImage(HPPDevice &device, vk::Format format, vk::ImageUsageFlags image_usage, vk::SampleCountFlagBits sample_count) : - HPPAllocated{handle, &device} + vkb::allocated::AllocatedCpp{handle, &device} { create_info.samples = sample_count; create_info.format = format; @@ -109,7 +109,7 @@ HPPImage::HPPImage(HPPDevice &device, } HPPImage::HPPImage(HPPImage &&other) noexcept : - HPPAllocated{std::move(other)}, + vkb::allocated::AllocatedCpp{std::move(other)}, create_info(std::exchange(other.create_info, {})), subresource(std::exchange(other.subresource, {})), views(std::exchange(other.views, {})) @@ -132,7 +132,7 @@ uint8_t *HPPImage::map() { LOGW("Mapping image memory that is not linear"); } - return Allocated::map(); + return vkb::allocated::AllocatedCpp::map(); } vk::ImageType HPPImage::get_type() const diff --git a/framework/core/image.h b/framework/core/image.h index 8e6e086267..43d3f1b9ac 100644 --- a/framework/core/image.h +++ b/framework/core/image.h @@ -35,10 +35,10 @@ namespace core class Image; using ImagePtr = std::unique_ptr; -struct ImageBuilder : public vkb::BuilderBaseC +struct ImageBuilder : public vkb::allocated::BuilderBaseC { private: - using Parent = vkb::BuilderBaseC; + using Parent = vkb::allocated::BuilderBaseC; public: ImageBuilder(VkExtent3D const &extent) : @@ -121,7 +121,7 @@ struct ImageBuilder : public vkb::BuilderBaseC }; class ImageView; -class Image : public allocated::Allocated +class Image : public vkb::allocated::AllocatedC { public: Image(vkb::Device &device, diff --git a/framework/core/image_core.cpp b/framework/core/image_core.cpp index 53ad876180..6968ecc8db 100644 --- a/framework/core/image_core.cpp +++ b/framework/core/image_core.cpp @@ -108,7 +108,7 @@ Image::Image(vkb::Device &device, } Image::Image(vkb::Device &device, ImageBuilder const &builder) : - Allocated{builder.get_allocation_create_info(), VK_NULL_HANDLE, &device}, create_info(builder.get_create_info()) + vkb::allocated::AllocatedC{builder.get_allocation_create_info(), VK_NULL_HANDLE, &device}, create_info(builder.get_create_info()) { set_handle(create_image(create_info)); subresource.arrayLayer = create_info.arrayLayers; @@ -120,7 +120,7 @@ Image::Image(vkb::Device &device, ImageBuilder const &builder) : } Image::Image(Device &device, VkImage handle, const VkExtent3D &extent, VkFormat format, VkImageUsageFlags image_usage, VkSampleCountFlagBits sample_count) : - Allocated{handle, &device} + vkb::allocated::AllocatedC{handle, &device} { create_info.extent = extent; create_info.imageType = find_image_type(extent); @@ -131,11 +131,9 @@ Image::Image(Device &device, VkImage handle, const VkExtent3D &extent, VkFormat subresource.mipLevel = create_info.mipLevels = 1; } -Image::Image(Image &&other) noexcept : - Allocated{std::move(other)}, - create_info{std::exchange(other.create_info, {})}, - subresource{std::exchange(other.subresource, {})}, - views(std::exchange(other.views, {})) +Image::Image(Image &&other) noexcept + : + vkb::allocated::AllocatedC{std::move(other)}, create_info{std::exchange(other.create_info, {})}, subresource{std::exchange(other.subresource, {})}, views(std::exchange(other.views, {})) { // Update image views references to this image to avoid dangling pointers for (auto &view : views)