Skip to content

Commit 19ba8ac

Browse files
committed
Refactor Buffer API
1 parent ee82cf1 commit 19ba8ac

File tree

6 files changed

+191
-116
lines changed

6 files changed

+191
-116
lines changed

guide/src/memory/buffers.md

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,64 +28,67 @@ void BufferDeleter::operator()(RawBuffer const& raw_buffer) const noexcept {
2828
}
2929
```
3030
31-
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. Leaving device buffers for later, add the `Buffer` alias and a create function:
31+
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 to. Add the related types and a create function:
3232
3333
```cpp
34-
using Buffer = Scoped<RawBuffer, BufferDeleter>;
34+
struct BufferCreateInfo {
35+
VmaAllocator allocator;
36+
vk::BufferUsageFlags usage;
37+
std::uint32_t queue_family;
38+
};
3539
36-
[[nodiscard]] auto create_host_buffer(VmaAllocator allocator,
37-
vk::BufferUsageFlags usage,
38-
vk::DeviceSize size) -> Buffer;
39-
```
40+
enum class BufferMemoryType : std::int8_t { Host, Device };
4041
41-
Add a helper function that can be reused for device buffers too later:
42+
[[nodiscard]] auto create_buffer(BufferCreateInfo const& create_info,
43+
BufferMemoryType memory_type,
44+
vk::DeviceSize size) -> Buffer;
4245
43-
```cpp
44-
[[nodiscard]] auto create_buffer(VmaAllocator allocator,
45-
VmaAllocationCreateInfo const& allocation_ci,
46-
vk::BufferUsageFlags const usage,
47-
vk::DeviceSize const size) -> Buffer {
46+
// ...
47+
auto vma::create_buffer(BufferCreateInfo const& create_info,
48+
BufferMemoryType const memory_type,
49+
vk::DeviceSize const size) -> Buffer {
4850
if (size == 0) {
4951
std::println(stderr, "Buffer cannot be 0-sized");
5052
return {};
5153
}
5254
55+
auto allocation_ci = VmaAllocationCreateInfo{};
56+
allocation_ci.flags =
57+
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
58+
auto usage = create_info.usage;
59+
if (memory_type == BufferMemoryType::Device) {
60+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
61+
// device buffers need to support TransferDst.
62+
usage |= vk::BufferUsageFlagBits::eTransferDst;
63+
} else {
64+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
65+
// host buffers can provide mapped memory.
66+
allocation_ci.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
67+
}
68+
5369
auto buffer_ci = vk::BufferCreateInfo{};
54-
buffer_ci.setSize(size).setUsage(usage);
70+
buffer_ci.setQueueFamilyIndices(create_info.queue_family)
71+
.setSize(size)
72+
.setUsage(usage);
5573
auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci);
5674
5775
VmaAllocation allocation{};
5876
VkBuffer buffer{};
5977
auto allocation_info = VmaAllocationInfo{};
6078
auto const result =
61-
vmaCreateBuffer(allocator, &vma_buffer_ci, &allocation_ci, &buffer,
62-
&allocation, &allocation_info);
79+
vmaCreateBuffer(create_info.allocator, &vma_buffer_ci, &allocation_ci,
80+
&buffer, &allocation, &allocation_info);
6381
if (result != VK_SUCCESS) {
6482
std::println(stderr, "Failed to create VMA Buffer");
6583
return {};
6684
}
6785
6886
return RawBuffer{
69-
.allocator = allocator,
87+
.allocator = create_info.allocator,
7088
.allocation = allocation,
7189
.buffer = buffer,
7290
.size = size,
7391
.mapped = allocation_info.pMappedData,
7492
};
7593
}
7694
```
77-
78-
Implement `create_host_buffer()`:
79-
80-
```cpp
81-
auto vma::create_host_buffer(VmaAllocator allocator,
82-
vk::BufferUsageFlags const usage,
83-
vk::DeviceSize const size) -> Buffer {
84-
auto allocation_ci = VmaAllocationCreateInfo{};
85-
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
86-
allocation_ci.flags =
87-
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
88-
VMA_ALLOCATION_CREATE_MAPPED_BIT;
89-
return create_buffer(allocator, allocation_ci, usage, size);
90-
}
91-
```

guide/src/memory/device_buffers.md

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,15 @@ This guide will only use device buffers for vertex buffers, where both vertex an
77
using ByteSpans = std::span<std::span<std::byte const> const>;
88

99
// returns a Device Buffer with each byte span sequentially written.
10-
[[nodiscard]] auto create_device_buffer(VmaAllocator allocator,
11-
vk::BufferUsageFlags usage,
10+
[[nodiscard]] auto create_device_buffer(BufferCreateInfo const& create_info,
1211
CommandBlock command_block,
1312
ByteSpans const& byte_spans) -> Buffer;
1413
```
1514
1615
Implement `create_device_buffer()`:
1716
1817
```cpp
19-
auto vma::create_device_buffer(VmaAllocator allocator,
20-
vk::BufferUsageFlags usage,
18+
auto vma::create_device_buffer(BufferCreateInfo const& create_info,
2119
CommandBlock command_block,
2220
ByteSpans const& byte_spans) -> Buffer {
2321
auto const total_size = std::accumulate(
@@ -26,18 +24,14 @@ auto vma::create_device_buffer(VmaAllocator allocator,
2624
return n + bytes.size();
2725
});
2826
29-
// create staging Host Buffer with TransferSrc usage.
30-
auto staging_buffer = create_host_buffer(
31-
allocator, vk::BufferUsageFlagBits::eTransferSrc, total_size);
27+
auto staging_ci = create_info;
28+
staging_ci.usage = vk::BufferUsageFlagBits::eTransferSrc;
3229
30+
// create staging Host Buffer with TransferSrc usage.
31+
auto staging_buffer =
32+
create_buffer(create_info, BufferMemoryType::Host, total_size);
3333
// create the Device Buffer, ensuring TransferDst usage.
34-
usage |= vk::BufferUsageFlagBits::eTransferDst;
35-
auto allocation_ci = VmaAllocationCreateInfo{};
36-
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
37-
allocation_ci.flags =
38-
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
39-
auto ret = create_buffer(allocator, allocation_ci, usage, total_size);
40-
34+
auto ret = create_buffer(create_info, BufferMemoryType::Device, total_size);
4135
// can't do anything if either buffer creation failed.
4236
if (!staging_buffer.get().buffer || !ret.get().buffer) { return {}; }
4337

guide/src/memory/vertex_buffer.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,22 @@ Add a VBO (Vertex Buffer Object) member and create it:
6868

6969
```cpp
7070
void App::create_vertex_buffer() {
71-
// we want to write 4x Vertex objects to a Host VertexBuffer.
72-
auto const buffer_ci = vma::BufferCreateInfo{
73-
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
74-
.size = 4 * sizeof(Vertex),
75-
.type = vma::BufferType::Host,
76-
};
77-
m_vbo = vma::create_buffer(m_allocator.get(), buffer_ci);
78-
7971
// vertices moved from the shader.
8072
static constexpr auto vertices_v = std::array{
8173
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
8274
Vertex{.position = {0.5f, -0.5f}, .color = {0.0f, 1.0f, 0.0f}},
8375
Vertex{.position = {0.0f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
8476
};
77+
78+
// we want to write vertices_v to a Host VertexBuffer.
79+
auto const buffer_ci = vma::BufferCreateInfo{
80+
.allocator = m_allocator.get(),
81+
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
82+
.queue_family = m_gpu.queue_family,
83+
};
84+
m_vbo = vma::create_buffer(buffer_ci, vma::BufferMemoryType::Host,
85+
sizeof(vertices_v));
86+
8587
// host buffers have a memory-mapped pointer available to memcpy data to.
8688
std::memcpy(m_vbo.get().mapped, vertices_v.data(), sizeof(vertices_v));
8789
}

src/app.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,14 @@ void App::create_vertex_buffer() {
290290
indices_bytes_v,
291291
};
292292
// we want to write total_bytes_v to a Device VertexBuffer | IndexBuffer.
293-
m_vbo = vma::create_device_buffer(m_allocator.get(),
294-
vk::BufferUsageFlagBits::eVertexBuffer |
295-
vk::BufferUsageFlagBits::eIndexBuffer,
296-
create_command_block(), total_bytes_v);
293+
auto const buffer_ci = vma::BufferCreateInfo{
294+
.allocator = m_allocator.get(),
295+
.usage = vk::BufferUsageFlagBits::eVertexBuffer |
296+
vk::BufferUsageFlagBits::eIndexBuffer,
297+
.queue_family = m_gpu.queue_family,
298+
};
299+
m_vbo = vma::create_device_buffer(buffer_ci, create_command_block(),
300+
total_bytes_v);
297301
}
298302

299303
auto App::asset_path(std::string_view const uri) const -> fs::path {

src/vma.cpp

Lines changed: 91 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,6 @@
55

66
namespace lvk {
77
namespace vma {
8-
namespace {
9-
[[nodiscard]] auto create_buffer(VmaAllocator allocator,
10-
VmaAllocationCreateInfo const& allocation_ci,
11-
vk::BufferUsageFlags const usage,
12-
vk::DeviceSize const size) -> Buffer {
13-
if (size == 0) {
14-
std::println(stderr, "Buffer cannot be 0-sized");
15-
return {};
16-
}
17-
18-
auto buffer_ci = vk::BufferCreateInfo{};
19-
buffer_ci.setSize(size).setUsage(usage);
20-
auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci);
21-
22-
VmaAllocation allocation{};
23-
VkBuffer buffer{};
24-
auto allocation_info = VmaAllocationInfo{};
25-
auto const result =
26-
vmaCreateBuffer(allocator, &vma_buffer_ci, &allocation_ci, &buffer,
27-
&allocation, &allocation_info);
28-
if (result != VK_SUCCESS) {
29-
std::println(stderr, "Failed to create VMA Buffer");
30-
return {};
31-
}
32-
33-
return RawBuffer{
34-
.allocator = allocator,
35-
.allocation = allocation,
36-
.buffer = buffer,
37-
.size = size,
38-
.mapped = allocation_info.pMappedData,
39-
};
40-
}
41-
} // namespace
42-
438
void Deleter::operator()(VmaAllocator allocator) const noexcept {
449
vmaDestroyAllocator(allocator);
4510
}
@@ -71,19 +36,55 @@ auto vma::create_allocator(vk::Instance const instance,
7136
throw std::runtime_error{"Failed to create Vulkan Memory Allocator"};
7237
}
7338

74-
auto vma::create_host_buffer(VmaAllocator allocator,
75-
vk::BufferUsageFlags const usage,
76-
vk::DeviceSize const size) -> Buffer {
39+
auto vma::create_buffer(BufferCreateInfo const& create_info,
40+
BufferMemoryType const memory_type,
41+
vk::DeviceSize const size) -> Buffer {
42+
if (size == 0) {
43+
std::println(stderr, "Buffer cannot be 0-sized");
44+
return {};
45+
}
46+
7747
auto allocation_ci = VmaAllocationCreateInfo{};
78-
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
7948
allocation_ci.flags =
80-
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
81-
VMA_ALLOCATION_CREATE_MAPPED_BIT;
82-
return create_buffer(allocator, allocation_ci, usage, size);
49+
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
50+
auto usage = create_info.usage;
51+
if (memory_type == BufferMemoryType::Device) {
52+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
53+
// device buffers need to support TransferDst.
54+
usage |= vk::BufferUsageFlagBits::eTransferDst;
55+
} else {
56+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
57+
// host buffers can provide mapped memory.
58+
allocation_ci.flags |= VMA_ALLOCATION_CREATE_MAPPED_BIT;
59+
}
60+
61+
auto buffer_ci = vk::BufferCreateInfo{};
62+
buffer_ci.setQueueFamilyIndices(create_info.queue_family)
63+
.setSize(size)
64+
.setUsage(usage);
65+
auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci);
66+
67+
VmaAllocation allocation{};
68+
VkBuffer buffer{};
69+
auto allocation_info = VmaAllocationInfo{};
70+
auto const result =
71+
vmaCreateBuffer(create_info.allocator, &vma_buffer_ci, &allocation_ci,
72+
&buffer, &allocation, &allocation_info);
73+
if (result != VK_SUCCESS) {
74+
std::println(stderr, "Failed to create VMA Buffer");
75+
return {};
76+
}
77+
78+
return RawBuffer{
79+
.allocator = create_info.allocator,
80+
.allocation = allocation,
81+
.buffer = buffer,
82+
.size = size,
83+
.mapped = allocation_info.pMappedData,
84+
};
8385
}
8486

85-
auto vma::create_device_buffer(VmaAllocator allocator,
86-
vk::BufferUsageFlags usage,
87+
auto vma::create_device_buffer(BufferCreateInfo const& create_info,
8788
CommandBlock command_block,
8889
ByteSpans const& byte_spans) -> Buffer {
8990
auto const total_size = std::accumulate(
@@ -92,18 +93,14 @@ auto vma::create_device_buffer(VmaAllocator allocator,
9293
return n + bytes.size();
9394
});
9495

95-
// create staging Host Buffer with TransferSrc usage.
96-
auto staging_buffer = create_host_buffer(
97-
allocator, vk::BufferUsageFlagBits::eTransferSrc, total_size);
96+
auto staging_ci = create_info;
97+
staging_ci.usage = vk::BufferUsageFlagBits::eTransferSrc;
9898

99+
// create staging Host Buffer with TransferSrc usage.
100+
auto staging_buffer =
101+
create_buffer(create_info, BufferMemoryType::Host, total_size);
99102
// create the Device Buffer, ensuring TransferDst usage.
100-
usage |= vk::BufferUsageFlagBits::eTransferDst;
101-
auto allocation_ci = VmaAllocationCreateInfo{};
102-
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
103-
allocation_ci.flags =
104-
VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
105-
auto ret = create_buffer(allocator, allocation_ci, usage, total_size);
106-
103+
auto ret = create_buffer(create_info, BufferMemoryType::Device, total_size);
107104
// can't do anything if either buffer creation failed.
108105
if (!staging_buffer.get().buffer || !ret.get().buffer) { return {}; }
109106

@@ -132,4 +129,44 @@ auto vma::create_device_buffer(VmaAllocator allocator,
132129

133130
return ret;
134131
}
132+
133+
auto vma::create_image(VmaAllocator allocator,
134+
ImageCreateInfo const& create_info) -> Image {
135+
if (create_info.extent.width == 0 || create_info.extent.height == 0) {
136+
std::println(stderr, "Images cannot have 0 width or height");
137+
return {};
138+
}
139+
auto image_ci = vk::ImageCreateInfo{};
140+
image_ci.setImageType(vk::ImageType::e2D)
141+
.setExtent({create_info.extent.width, create_info.extent.height, 1})
142+
.setFormat(create_info.format)
143+
.setUsage(create_info.usage)
144+
.setArrayLayers(1)
145+
.setMipLevels(create_info.levels)
146+
.setSamples(vk::SampleCountFlagBits::e1)
147+
.setTiling(vk::ImageTiling::eOptimal)
148+
.setInitialLayout(vk::ImageLayout::eUndefined)
149+
.setQueueFamilyIndices(create_info.queue_family);
150+
auto const vk_image_ci = static_cast<VkImageCreateInfo>(image_ci);
151+
152+
auto allocation_ci = VmaAllocationCreateInfo{};
153+
allocation_ci.usage = VMA_MEMORY_USAGE_AUTO;
154+
VkImage image{};
155+
VmaAllocation allocation{};
156+
auto const result = vmaCreateImage(allocator, &vk_image_ci, &allocation_ci,
157+
&image, &allocation, {});
158+
if (result != VK_SUCCESS) {
159+
std::println(stderr, "Failed to create VMA Image");
160+
return {};
161+
}
162+
163+
return RawImage{
164+
.allocator = allocator,
165+
.allocation = allocation,
166+
.image = image,
167+
.extent = create_info.extent,
168+
.format = create_info.format,
169+
.levels = create_info.levels,
170+
};
171+
}
135172
} // namespace lvk

0 commit comments

Comments
 (0)