Skip to content

Commit 23f4b50

Browse files
committed
Refactor vma::Buffer API
1 parent 6b1b15f commit 23f4b50

File tree

8 files changed

+133
-281
lines changed

8 files changed

+133
-281
lines changed

guide/src/SUMMARY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@
3939
- [Memory Allocation](memory/README.md)
4040
- [Vulkan Memory Allocator](memory/vma.md)
4141
- [Buffers](memory/buffers.md)
42-
- [Vertex Buffer](memory/vertex_buffer.md)
42+
- [Host Vertex Buffer](memory/host_vertex_buffer.md)

guide/src/memory/buffers.md

Lines changed: 37 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@ First add the RAII wrapper components for VMA buffers:
44

55
```cpp
66
struct RawBuffer {
7+
[[nodiscard]] auto mapped_span() const -> std::span<std::byte> {
8+
return std::span{static_cast<std::byte*>(mapped), size};
9+
}
10+
11+
auto operator==(RawBuffer const& rhs) const -> bool = default;
12+
713
VmaAllocator allocator{};
814
VmaAllocation allocation{};
915
vk::Buffer buffer{};
10-
vk::DeviceSize capacity{};
1116
vk::DeviceSize size{};
1217
void* mapped{};
13-
14-
auto operator==(RawBuffer const& rhs) const -> bool = default;
1518
};
1619

1720
struct BufferDeleter {
@@ -25,82 +28,30 @@ void BufferDeleter::operator()(RawBuffer const& raw_buffer) const noexcept {
2528
}
2629
```
2730
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:
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:
2932
3033
```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;
34+
using Buffer = Scoped<RawBuffer, BufferDeleter>;
6135
62-
Scoped<RawBuffer, BufferDeleter> m_buffer{};
63-
vk::BufferUsageFlags m_usage{};
64-
};
36+
[[nodiscard]] auto create_host_buffer(VmaAllocator allocator,
37+
vk::BufferUsageFlags usage,
38+
vk::DeviceSize size) -> Buffer;
6539
```
6640

67-
`resize()` and `create()` are separate because the former uses the existing `m_buffer`'s allocator. The implementation:
41+
Add a helper function that can be reused for device buffers too later:
6842

6943
```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;
44+
[[nodiscard]] auto create_buffer(VmaAllocator allocator,
45+
VmaAllocationCreateInfo const& allocation_ci,
46+
vk::BufferUsageFlags const usage,
47+
vk::DeviceSize const size) -> Buffer {
48+
if (size == 0) {
49+
std::println(stderr, "Buffer cannot be 0-sized");
50+
return {};
10051
}
10152

10253
auto buffer_ci = vk::BufferCreateInfo{};
103-
buffer_ci.setSize(size).setUsage(m_usage);
54+
buffer_ci.setSize(size).setUsage(usage);
10455
auto vma_buffer_ci = static_cast<VkBufferCreateInfo>(buffer_ci);
10556

10657
VmaAllocation allocation{};
@@ -111,17 +62,30 @@ auto Buffer::create(VmaAllocator allocator, Type const type,
11162
&allocation, &allocation_info);
11263
if (result != VK_SUCCESS) {
11364
std::println(stderr, "Failed to create VMA Buffer");
114-
return false;
65+
return {};
11566
}
11667

117-
m_buffer = RawBuffer{
68+
return RawBuffer{
11869
.allocator = allocator,
11970
.allocation = allocation,
12071
.buffer = buffer,
121-
.capacity = size,
12272
.size = size,
12373
.mapped = allocation_info.pMappedData,
12474
};
125-
return true;
75+
}
76+
```
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);
12690
}
12791
```
Lines changed: 20 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# Vertex Buffer
1+
# Host Vertex Buffer
22

3-
The goal here is to move the hard-coded vertices in the shader to application code. For the time being we will use an ad-hoc host `vma::Buffer` and focus more on the rest of the infrastructure like vertex attributes.
3+
The goal here is to move the hard-coded vertices in the shader to application code. For the time being we will use an ad-hoc Host type `vma::Buffer` and focus more on the rest of the infrastructure like vertex attributes.
44

55
First add a new header, `vertex.hpp`:
66

@@ -9,11 +9,7 @@ struct Vertex {
99
glm::vec2 position{};
1010
glm::vec3 color{};
1111
};
12-
```
13-
14-
In `app.cpp`, store the vertex input:
1512

16-
```cpp
1713
// two vertex attributes: position at 0, color at 1.
1814
constexpr auto vertex_attributes_v = std::array{
1915
// the format matches the type and layout of data: vec2 => 2x 32-bit floats.
@@ -30,21 +26,24 @@ constexpr auto vertex_bindings_v = std::array{
3026
vk::VertexInputBindingDescription2EXT{0, sizeof(Vertex),
3127
vk::VertexInputRate::eVertex, 1},
3228
};
33-
34-
constexpr auto vertex_input_v = ShaderVertexInput{
35-
.attributes = vertex_attributes_v,
36-
.bindings = vertex_bindings_v,
37-
};
3829
```
3930
40-
Add `vertex_input_v` to the Shader Create Info:
31+
Add the vertex attributes and bindings to the Shader Create Info:
4132
4233
```cpp
34+
// ...
35+
static constexpr auto vertex_input_v = ShaderVertexInput{
36+
.attributes = vertex_attributes_v,
37+
.bindings = vertex_bindings_v,
38+
};
4339
auto const shader_ci = ShaderProgram::CreateInfo{
44-
// ...
40+
.device = *m_device,
41+
.vertex_spirv = vertex_spirv,
42+
.fragment_spirv = fragment_spirv,
4543
.vertex_input = vertex_input_v,
4644
.set_layouts = {},
4745
};
46+
// ...
4847
```
4948

5049
With the vertex input defined, we can update the vertex shader and recompile it:
@@ -69,22 +68,22 @@ Add a VBO (Vertex Buffer Object) member and create it:
6968

7069
```cpp
7170
void App::create_vertex_buffer() {
72-
// we want to write 3x Vertex objects to a Host VertexBuffer.
73-
auto const buffer_ci = vma::Buffer::CreateInfo{
71+
// we want to write 4x Vertex objects to a Host VertexBuffer.
72+
auto const buffer_ci = vma::BufferCreateInfo{
7473
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
75-
.size = 3 * sizeof(Vertex),
74+
.size = 4 * sizeof(Vertex),
7675
.type = vma::BufferType::Host,
7776
};
78-
m_vbo.emplace(m_allocator.get(), buffer_ci);
77+
m_vbo = vma::create_buffer(m_allocator.get(), buffer_ci);
7978

80-
// vertices that were previously hard-coded in the shader.
79+
// vertices moved from the shader.
8180
static constexpr auto vertices_v = std::array{
8281
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
8382
Vertex{.position = {0.5f, -0.5f}, .color = {0.0f, 1.0f, 0.0f}},
8483
Vertex{.position = {0.0f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
8584
};
8685
// host buffers have a memory-mapped pointer available to memcpy data to.
87-
std::memcpy(m_vbo->get_raw().mapped, vertices_v.data(), sizeof(vertices_v));
86+
std::memcpy(m_vbo.get().mapped, vertices_v.data(), sizeof(vertices_v));
8887
}
8988
```
9089

@@ -98,51 +97,6 @@ command_buffer.bindVertexBuffers(0, m_vbo->get_raw().buffer,
9897
command_buffer.draw(3, 1, 0, 0);
9998
```
10099

101-
You should see the same triangle as before. But now we can use whatever set of vertices we like! To change it to a quad / rectangle, let's utilize an index buffer: this will reduce vertex duplication in general.
102-
103-
```cpp
104-
void App::create_vertex_buffer() {
105-
// we want to write 4x Vertex objects to a Host VertexBuffer.
106-
auto buffer_ci = vma::Buffer::CreateInfo{
107-
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
108-
.size = 4 * sizeof(Vertex),
109-
.type = vma::BufferType::Host,
110-
};
111-
m_vbo.emplace(m_allocator.get(), buffer_ci);
112-
113-
// vertices that form a quad.
114-
static constexpr auto vertices_v = std::array{
115-
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
116-
Vertex{.position = {0.5f, -0.5f}, .color = {0.0f, 1.0f, 0.0f}},
117-
Vertex{.position = {0.5f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
118-
Vertex{.position = {-0.5f, 0.5f}, .color = {1.0f, 1.0f, 0.0f}},
119-
};
120-
// host buffers have a memory-mapped pointer available to memcpy data to.
121-
std::memcpy(m_vbo->get_raw().mapped, vertices_v.data(), sizeof(vertices_v));
122-
123-
// prepare to write 6x u32 indices to a Host IndexBuffer.
124-
buffer_ci = {
125-
.usage = vk::BufferUsageFlagBits::eIndexBuffer,
126-
.size = 6 * sizeof(std::uint32_t),
127-
.type = vma::BufferType::Host,
128-
};
129-
m_ibo.emplace(m_allocator.get(), buffer_ci);
130-
static constexpr auto indices_v = std::array{
131-
0u, 1u, 2u, 2u, 3u, 0u,
132-
};
133-
std::memcpy(m_ibo->get_raw().mapped, indices_v.data(), sizeof(indices_v));
134-
}
135-
```
136-
137-
Bind the index buffer and use the `drawIndexed()` command:
100+
You should see the same triangle as before. But now we can use whatever set of vertices we like! The Primitive Topology is Triange List by default, so every three vertices in the array is drawn as a triangle, eg for 9 vertices: `[[0, 1, 2], [3, 4, 5], [6, 7, 8]]`, where each inner `[]` represents a triangle comprised of the vertices at those indices.
138101

139-
```cpp
140-
// single VBO at binding 0 at no offset.
141-
command_buffer.bindVertexBuffers(0, m_vbo->get_raw().buffer,
142-
vk::DeviceSize{});
143-
// IBO with u32 indices at no offset.
144-
command_buffer.bindIndexBuffer(m_ibo->get_raw().buffer, 0,
145-
vk::IndexType::eUint32);
146-
// m_ibo has 6 indices.
147-
command_buffer.drawIndexed(6, 1, 0, 0, 0);
148-
```
102+
Host Vertex Buffers are useful for primitives that are temporary and/or frequently changing, such as UI objects. A 2D framework can use such VBOs exclusively: a simple approach would be a pool of buffers per virtual frame where for each draw a buffer is obtained from the current virtual frame's pool and vertices are copied in.

src/app.cpp

Lines changed: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,6 @@ namespace lvk {
1212
using namespace std::chrono_literals;
1313

1414
namespace {
15-
// two vertex attributes: position at 0, color at 1.
16-
constexpr auto vertex_attributes_v = std::array{
17-
// the format matches the type and layout of data: vec2 => 2x 32-bit floats.
18-
vk::VertexInputAttributeDescription2EXT{0, 0, vk::Format::eR32G32Sfloat,
19-
offsetof(Vertex, position)},
20-
// vec3 => 3x 32-bit floats
21-
vk::VertexInputAttributeDescription2EXT{1, 0, vk::Format::eR32G32B32Sfloat,
22-
offsetof(Vertex, color)},
23-
};
24-
25-
// one vertex binding at location 0.
26-
constexpr auto vertex_bindings_v = std::array{
27-
// we are using interleaved data with a stride of sizeof(Vertex).
28-
vk::VertexInputBindingDescription2EXT{0, sizeof(Vertex),
29-
vk::VertexInputRate::eVertex, 1},
30-
};
31-
32-
constexpr auto vertex_input_v = ShaderVertexInput{
33-
.attributes = vertex_attributes_v,
34-
.bindings = vertex_bindings_v,
35-
};
36-
3715
[[nodiscard]] auto locate_assets_dir() -> fs::path {
3816
// look for '<path>/assets/', starting from the working
3917
// directory and walking up the parent directory tree.
@@ -262,6 +240,10 @@ void App::create_shader() {
262240
auto const vertex_spirv = to_spir_v(asset_path("shader.vert"));
263241
auto const fragment_spirv = to_spir_v(asset_path("shader.frag"));
264242

243+
static constexpr auto vertex_input_v = ShaderVertexInput{
244+
.attributes = vertex_attributes_v,
245+
.bindings = vertex_bindings_v,
246+
};
265247
auto const shader_ci = ShaderProgram::CreateInfo{
266248
.device = *m_device,
267249
.vertex_spirv = vertex_spirv,
@@ -273,35 +255,19 @@ void App::create_shader() {
273255
}
274256

275257
void App::create_vertex_buffer() {
276-
// we want to write 4x Vertex objects to a Host VertexBuffer.
277-
auto buffer_ci = vma::Buffer::CreateInfo{
278-
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
279-
.size = 4 * sizeof(Vertex),
280-
.type = vma::BufferType::Host,
281-
};
282-
m_vbo.emplace(m_allocator.get(), buffer_ci);
283-
284-
// vertices that form a quad.
258+
// vertices previously hard-coded in the vertex shader.
285259
static constexpr auto vertices_v = std::array{
286260
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
287261
Vertex{.position = {0.5f, -0.5f}, .color = {0.0f, 1.0f, 0.0f}},
288-
Vertex{.position = {0.5f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
289-
Vertex{.position = {-0.5f, 0.5f}, .color = {1.0f, 1.0f, 0.0f}},
262+
Vertex{.position = {0.0f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
290263
};
291-
// host buffers have a memory-mapped pointer available to memcpy data to.
292-
std::memcpy(m_vbo->get_raw().mapped, vertices_v.data(), sizeof(vertices_v));
264+
// we want to write vertices_v to a Host VertexBuffer.
265+
m_vbo = vma::create_host_buffer(m_allocator.get(),
266+
vk::BufferUsageFlagBits::eVertexBuffer,
267+
sizeof(vertices_v));
293268

294-
// prepare to write 6x u32 indices to a Host IndexBuffer.
295-
buffer_ci = {
296-
.usage = vk::BufferUsageFlagBits::eIndexBuffer,
297-
.size = 6 * sizeof(std::uint32_t),
298-
.type = vma::BufferType::Host,
299-
};
300-
m_ibo.emplace(m_allocator.get(), buffer_ci);
301-
static constexpr auto indices_v = std::array{
302-
0u, 1u, 2u, 2u, 3u, 0u,
303-
};
304-
std::memcpy(m_ibo->get_raw().mapped, indices_v.data(), sizeof(indices_v));
269+
// host buffers have a memory-mapped pointer available to memcpy data to.
270+
std::memcpy(m_vbo.get().mapped, vertices_v.data(), sizeof(vertices_v));
305271
}
306272

307273
auto App::asset_path(std::string_view const uri) const -> fs::path {
@@ -483,12 +449,8 @@ void App::inspect() {
483449
void App::draw(vk::CommandBuffer const command_buffer) const {
484450
m_shader->bind(command_buffer, m_framebuffer_size);
485451
// single VBO at binding 0 at no offset.
486-
command_buffer.bindVertexBuffers(0, m_vbo->get_raw().buffer,
487-
vk::DeviceSize{});
488-
// IBO with u32 indices at no offset.
489-
command_buffer.bindIndexBuffer(m_ibo->get_raw().buffer, 0,
490-
vk::IndexType::eUint32);
491-
// m_ibo has 6 indices.
492-
command_buffer.drawIndexed(6, 1, 0, 0, 0);
452+
command_buffer.bindVertexBuffers(0, m_vbo.get().buffer, vk::DeviceSize{});
453+
// m_vbo has 3 vertices.
454+
command_buffer.draw(3, 1, 0, 0);
493455
}
494456
} // namespace lvk

0 commit comments

Comments
 (0)