Skip to content

Commit d87b2e6

Browse files
committed
WIP: Buffers
1 parent f9b77b8 commit d87b2e6

File tree

7 files changed

+256
-4
lines changed

7 files changed

+256
-4
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,4 @@
3838

3939
- [Memory Allocation](memory/README.md)
4040
- [Vulkan Memory Allocator](memory/vma.md)
41+
- [Buffers](memory/buffers.md)

guide/src/memory/buffers.md

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
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+
```

guide/src/memory/vma.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ using Allocator = Scoped<VmaAllocator, Deleter>;
2626
} // namespace lvk::vma
2727

2828
// vma.cpp
29-
void vma::Deleter::operator()(VmaAllocator allocator) const noexcept {
29+
void Deleter::operator()(VmaAllocator allocator) const noexcept {
3030
vmaDestroyAllocator(allocator);
3131
}
3232

33+
// ...
3334
auto vma::create_allocator(vk::Instance const instance,
3435
vk::PhysicalDevice const physical_device,
3536
vk::Device const device) -> Allocator {

src/app.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ void App::run() {
7777
create_surface();
7878
select_gpu();
7979
create_device();
80+
create_allocator();
8081
create_swapchain();
8182
create_render_sync();
8283
create_imgui();

src/scoped.hpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
namespace lvk {
66
template <typename Type>
7-
concept Scopeable =
8-
std::equality_comparable<Type> && std::is_default_constructible_v<Type>;
7+
concept Scopeable = std::equality_comparable<Type>;
98

109
template <Scopeable Type, typename Deleter>
1110
class Scoped {

src/vma.cpp

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,82 @@
11
#include <vma.hpp>
2+
#include <print>
23
#include <stdexcept>
34

45
namespace lvk {
5-
void vma::Deleter::operator()(VmaAllocator allocator) const noexcept {
6+
namespace vma {
7+
namespace {
8+
[[nodiscard]] constexpr auto positive_size(vk::DeviceSize const in) {
9+
return in > 0 ? in : 1;
10+
}
11+
} // namespace
12+
13+
void Deleter::operator()(VmaAllocator allocator) const noexcept {
614
vmaDestroyAllocator(allocator);
715
}
816

17+
void BufferDeleter::operator()(RawBuffer const& raw_buffer) const noexcept {
18+
vmaDestroyBuffer(raw_buffer.allocator, raw_buffer.buffer,
19+
raw_buffer.allocation);
20+
}
21+
22+
Buffer::Buffer(VmaAllocator allocator, CreateInfo const& create_info)
23+
: m_usage(create_info.usage) {
24+
if (create_info.type == Type::Device) {
25+
// device buffers require a transfer operation to copy data.
26+
m_usage |= vk::BufferUsageFlagBits::eTransferDst;
27+
}
28+
create(allocator, create_info.type, create_info.size);
29+
}
30+
31+
auto Buffer::resize(vk::DeviceSize const size) -> bool {
32+
if (size <= m_buffer.get().capacity) {
33+
m_buffer.get().size = size;
34+
return true;
35+
}
36+
return create(m_buffer.get().allocator, get_type(), size);
37+
}
38+
39+
auto Buffer::create(VmaAllocator allocator, Type const type,
40+
vk::DeviceSize size) -> bool {
41+
// buffers cannot be zero sized.
42+
size = positive_size(size);
43+
auto allocation_ci = VmaAllocationCreateInfo{};
44+
allocation_ci.flags =
45+
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
46+
if (type == BufferType::Device) {
47+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
48+
} else {
49+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
50+
allocation_ci.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
51+
}
52+
53+
auto buffer_ci = vk::BufferCreateInfo{};
54+
buffer_ci.setSize(size).setUsage(m_usage);
55+
auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci);
56+
57+
VmaAllocation allocation{};
58+
VkBuffer buffer{};
59+
auto allocation_info = VmaAllocationInfo{};
60+
auto const result =
61+
vmaCreateBuffer(allocator, &vma_buffer_ci, &allocation_ci, &buffer,
62+
&allocation, &allocation_info);
63+
if (result != VK_SUCCESS) {
64+
std::println(stderr, "Failed to create VMA Buffer");
65+
return false;
66+
}
67+
68+
m_buffer = RawBuffer{
69+
.allocator = allocator,
70+
.allocation = allocation,
71+
.buffer = buffer,
72+
.capacity = size,
73+
.size = size,
74+
.mapped = allocation_info.pMappedData,
75+
};
76+
return true;
77+
}
78+
} // namespace vma
79+
980
auto vma::create_allocator(vk::Instance const instance,
1081
vk::PhysicalDevice const physical_device,
1182
vk::Device const device) -> Allocator {

src/vma.hpp

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <vk_mem_alloc.h>
33
#include <scoped.hpp>
44
#include <vulkan/vulkan.hpp>
5+
#include <cstdint>
56

67
namespace lvk::vma {
78
struct Deleter {
@@ -13,4 +14,55 @@ using Allocator = Scoped<VmaAllocator, Deleter>;
1314
[[nodiscard]] auto create_allocator(vk::Instance instance,
1415
vk::PhysicalDevice physical_device,
1516
vk::Device device) -> Allocator;
17+
18+
struct RawBuffer {
19+
VmaAllocator allocator{};
20+
VmaAllocation allocation{};
21+
vk::Buffer buffer{};
22+
vk::DeviceSize capacity{};
23+
vk::DeviceSize size{};
24+
void* mapped{};
25+
26+
auto operator==(RawBuffer const& rhs) const -> bool = default;
27+
};
28+
29+
struct BufferDeleter {
30+
void operator()(RawBuffer const& raw_buffer) const noexcept;
31+
};
32+
33+
enum class BufferType : std::int8_t { Host, Device };
34+
35+
struct BufferCreateInfo {
36+
vk::BufferUsageFlags usage{};
37+
vk::DeviceSize size{};
38+
BufferType type{BufferType::Host};
39+
};
40+
41+
class Buffer {
42+
public:
43+
using CreateInfo = BufferCreateInfo;
44+
using Type = BufferType;
45+
46+
explicit Buffer(VmaAllocator allocator, CreateInfo const& create_info);
47+
48+
[[nodiscard]] auto get_type() const -> Type {
49+
return m_buffer.get().mapped == nullptr ? Type::Device : Type::Host;
50+
}
51+
52+
[[nodiscard]] auto get_usage() const -> vk::BufferUsageFlags {
53+
return m_usage;
54+
}
55+
56+
[[nodiscard]] auto get_raw() const -> RawBuffer const& {
57+
return m_buffer.get();
58+
}
59+
60+
auto resize(vk::DeviceSize size) -> bool;
61+
62+
private:
63+
auto create(VmaAllocator allocator, Type type, vk::DeviceSize size) -> bool;
64+
65+
Scoped<RawBuffer, BufferDeleter> m_buffer{};
66+
vk::BufferUsageFlags m_usage{};
67+
};
1668
} // namespace lvk::vma

0 commit comments

Comments
 (0)