Program Listing for File VulkanBufferManager.ixx

Program Listing for File VulkanBufferManager.ixx#

Return to documentation for file (Src/GraphicsEngineVulkan/vulkan_base/VulkanBufferManager.ixx)

module;
#include <memory>
#include <cstring>
#include <vector>
#include <vulkan/vulkan.hpp>

export module kataglyphis.vulkan.buffer_manager;

import kataglyphis.vulkan.buffer;
import kataglyphis.vulkan.device;

export namespace Kataglyphis {
class VulkanBufferManager
{
  public:
    VulkanBufferManager();
    // The reusable staging buffer makes the manager move-only.
    VulkanBufferManager(const VulkanBufferManager &) = delete;
    VulkanBufferManager &operator=(const VulkanBufferManager &) = delete;
    VulkanBufferManager(VulkanBufferManager &&) noexcept = default;
    VulkanBufferManager &operator=(VulkanBufferManager &&) noexcept = default;

    bool copyBuffer(vk::Device device,
      vk::Queue transfer_queue,
      vk::CommandPool transfer_command_pool,
      VulkanBuffer &src_buffer,
      VulkanBuffer &dst_buffer,
      vk::DeviceSize buffer_size);

    static void copyImageBuffer(vk::CommandBuffer command_buffer, vk::Buffer src_buffer, vk::Image image, uint32_t width, uint32_t height);

    template<typename T>
    bool createBufferAndUploadVectorOnDevice(const std::shared_ptr<VulkanDevice> &device,
      vk::CommandPool commandPool,
      VulkanBuffer &vulkanBuffer,
      vk::BufferUsageFlags dstBufferUsageFlags,
      vk::MemoryPropertyFlags dstBufferMemoryPropertyFlags,
      const std::vector<T> &data,
      vk::MemoryAllocateFlags dstBufferMemoryAllocateFlags = {},
      vk::Queue transfer_queue = {});

    // Releases the reusable staging buffer. Must run (or the manager be
    // destroyed) while the device's VMA allocator is still alive, i.e.
    // before VulkanDevice::cleanUp(). Safe to call multiple times; the
    // staging buffer is lazily recreated on the next upload.
    void cleanUp();

    ~VulkanBufferManager();

  private:
    // Persistently mapped (VMA_ALLOCATION_CREATE_MAPPED_BIT) staging buffer
    // reused across uploads instead of being created/destroyed per upload.
    // Grown geometrically whenever an upload exceeds its capacity. Reuse is
    // safe because every upload submit is fence-synchronized before return.
    VulkanBuffer stagingBuffer;
    vk::DeviceSize stagingBufferCapacity{ 0 };

    void ensureStagingBufferCapacity(const std::shared_ptr<VulkanDevice> &device, vk::DeviceSize size);
};

template<typename T>
inline bool VulkanBufferManager::createBufferAndUploadVectorOnDevice(const std::shared_ptr<VulkanDevice> &device,
  vk::CommandPool commandPool,
  VulkanBuffer &vulkanBuffer,
  vk::BufferUsageFlags dstBufferUsageFlags,
  vk::MemoryPropertyFlags dstBufferMemoryPropertyFlags,
  const std::vector<T> &data,
  vk::MemoryAllocateFlags dstBufferMemoryAllocateFlags,
  vk::Queue transfer_queue)
{
    vk::DeviceSize bufferSize = sizeof(T) * data.size();
    if (bufferSize == 0) {
        bufferSize = sizeof(uint32_t);
        vulkanBuffer.create(
          device, bufferSize, dstBufferUsageFlags, dstBufferMemoryPropertyFlags, dstBufferMemoryAllocateFlags);
        return true;
    }

    ensureStagingBufferCapacity(device, bufferSize);

    // The staging buffer is host-visible and therefore persistently mapped by VMA.
    std::memcpy(stagingBuffer.getMappedData(), data.data(), static_cast<size_t>(bufferSize));

    vulkanBuffer.create(
      device, bufferSize, dstBufferUsageFlags, dstBufferMemoryPropertyFlags, dstBufferMemoryAllocateFlags);

    vk::Queue const queue = transfer_queue ? transfer_queue : device->getGraphicsQueue();

    // stagingBuffer is intentionally kept alive for reuse by the next upload;
    // it is released in cleanUp().
    return copyBuffer(device->getLogicalDevice(), queue, commandPool, stagingBuffer, vulkanBuffer, bufferSize);
}
}// namespace Kataglyphis