|
| 1 | +# Buffers |
| 2 | + |
| 3 | +First add the RAII wrapper components for VMA buffers: |
| 4 | + |
| 5 | +```cpp |
| 6 | +struct RawBuffer { |
| 7 | + VmaAllocator allocator{}; |
| 8 | + VmaAllocation allocation{}; |
| 9 | + vk::Buffer buffer{}; |
| 10 | + vk::DeviceSize capacity{}; |
| 11 | + vk::DeviceSize size{}; |
| 12 | + void* mapped{}; |
| 13 | + |
| 14 | + auto operator==(RawBuffer const& rhs) const -> bool = default; |
| 15 | +}; |
| 16 | + |
| 17 | +struct BufferDeleter { |
| 18 | + void operator()(RawBuffer const& raw_buffer) const noexcept; |
| 19 | +}; |
| 20 | + |
| 21 | +// ... |
| 22 | +void BufferDeleter::operator()(RawBuffer const& raw_buffer) const noexcept { |
| 23 | + vmaDestroyBuffer(raw_buffer.allocator, raw_buffer.buffer, |
| 24 | + raw_buffer.allocation); |
| 25 | +} |
| 26 | +``` |
| 27 | +
|
| 28 | +Buffers can be backed by host (RAM) or device (VRAM) memory: the former is mappable and thus useful for data that changes every frame, latter is faster to access for the GPU but needs more complex methods to copy data from the CPU to it. Add the relevant subset of parameters and the RAII wrapper: |
| 29 | +
|
| 30 | +```cpp |
| 31 | +enum class BufferType : std::int8_t { Host, Device }; |
| 32 | +
|
| 33 | +struct BufferCreateInfo { |
| 34 | + vk::BufferUsageFlags usage{}; |
| 35 | + vk::DeviceSize size{}; |
| 36 | + BufferType type{BufferType::Host}; |
| 37 | +}; |
| 38 | +
|
| 39 | +class Buffer { |
| 40 | + public: |
| 41 | + using CreateInfo = BufferCreateInfo; |
| 42 | +
|
| 43 | + explicit Buffer(VmaAllocator allocator, CreateInfo const& create_info); |
| 44 | +
|
| 45 | + [[nodiscard]] auto get_type() const -> Type { |
| 46 | + return m_buffer.get().mapped == nullptr ? Type::Device : Type::Host; |
| 47 | + } |
| 48 | +
|
| 49 | + [[nodiscard]] auto get_usage() const -> vk::BufferUsageFlags { |
| 50 | + return m_usage; |
| 51 | + } |
| 52 | +
|
| 53 | + [[nodiscard]] auto get_raw() const -> RawBuffer const& { |
| 54 | + return m_buffer.get(); |
| 55 | + } |
| 56 | +
|
| 57 | + auto resize(vk::DeviceSize size) -> bool; |
| 58 | +
|
| 59 | + private: |
| 60 | + auto create(VmaAllocator allocator, vk::DeviceSize size) -> bool; |
| 61 | +
|
| 62 | + Scoped<RawBuffer, BufferDeleter> m_buffer{}; |
| 63 | + vk::BufferUsageFlags m_usage{}; |
| 64 | +}; |
| 65 | +``` |
| 66 | + |
| 67 | +`resize()` and `create()` are separate because the former uses the existing `m_buffer`'s allocator. The implementation: |
| 68 | + |
| 69 | +```cpp |
| 70 | +[[nodiscard]] constexpr auto positive_size(vk::DeviceSize const in) { |
| 71 | + return in > 0 ? in : 1; |
| 72 | +} |
| 73 | + |
| 74 | +// ... |
| 75 | +Buffer::Buffer(VmaAllocator allocator, CreateInfo const& create_info) |
| 76 | + : m_usage(create_info.usage) { |
| 77 | + create(allocator, create_info.type, create_info.size); |
| 78 | +} |
| 79 | + |
| 80 | +auto Buffer::resize(vk::DeviceSize const size) -> bool { |
| 81 | + if (size <= m_buffer.get().capacity) { |
| 82 | + m_buffer.get().size = size; |
| 83 | + return true; |
| 84 | + } |
| 85 | + return create(m_buffer.get().allocator, get_type(), size); |
| 86 | +} |
| 87 | + |
| 88 | +auto Buffer::create(VmaAllocator allocator, Type const type, |
| 89 | + vk::DeviceSize size) -> bool { |
| 90 | + // buffers cannot be zero sized. |
| 91 | + size = positive_size(size); |
| 92 | + auto allocation_ci = VmaAllocationCreateInfo{}; |
| 93 | + allocation_ci.flags = |
| 94 | + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; |
| 95 | + if (type == BufferType::Device) { |
| 96 | + allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; |
| 97 | + } else { |
| 98 | + allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST; |
| 99 | + allocation_ci.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT; |
| 100 | + } |
| 101 | + |
| 102 | + auto buffer_ci = vk::BufferCreateInfo{}; |
| 103 | + buffer_ci.setSize(size).setUsage(m_usage); |
| 104 | + auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci); |
| 105 | + |
| 106 | + VmaAllocation allocation{}; |
| 107 | + VkBuffer buffer{}; |
| 108 | + auto allocation_info = VmaAllocationInfo{}; |
| 109 | + auto const result = |
| 110 | + vmaCreateBuffer(allocator, &vma_buffer_ci, &allocation_ci, &buffer, |
| 111 | + &allocation, &allocation_info); |
| 112 | + if (result != VK_SUCCESS) { |
| 113 | + std::println(stderr, "Failed to create VMA Buffer"); |
| 114 | + return false; |
| 115 | + } |
| 116 | + |
| 117 | + m_buffer = RawBuffer{ |
| 118 | + .allocator = allocator, |
| 119 | + .allocation = allocation, |
| 120 | + .buffer = buffer, |
| 121 | + .capacity = size, |
| 122 | + .size = size, |
| 123 | + .mapped = allocation_info.pMappedData, |
| 124 | + }; |
| 125 | + return true; |
| 126 | +} |
| 127 | +``` |
0 commit comments