Skip to content

Commit 0f0ddd2

Browse files
committed
Vertex and Index Buffers
1 parent d87b2e6 commit 0f0ddd2

File tree

8 files changed

+235
-17
lines changed

8 files changed

+235
-17
lines changed

assets/shader.vert

-416 Bytes
Binary file not shown.

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +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)

guide/src/memory/ibo_quad.png

73 KB
Loading

guide/src/memory/vertex_buffer.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Vertex Buffer
2+
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.
4+
5+
First add a new header, `vertex.hpp`:
6+
7+
```cpp
8+
struct Vertex {
9+
glm::vec2 position{};
10+
glm::vec3 color{};
11+
};
12+
```
13+
14+
In `app.cpp`, store the vertex input:
15+
16+
```cpp
17+
// two vertex attributes: position at 0, color at 1.
18+
constexpr auto vertex_attributes_v = std::array{
19+
// the format matches the type and layout of data: vec2 => 2x 32-bit floats.
20+
vk::VertexInputAttributeDescription2EXT{0, 0, vk::Format::eR32G32Sfloat,
21+
offsetof(Vertex, position)},
22+
// vec3 => 3x 32-bit floats
23+
vk::VertexInputAttributeDescription2EXT{1, 0, vk::Format::eR32G32B32Sfloat,
24+
offsetof(Vertex, color)},
25+
};
26+
27+
// one vertex binding at location 0.
28+
constexpr auto vertex_bindings_v = std::array{
29+
// we are using interleaved data with a stride of sizeof(Vertex).
30+
vk::VertexInputBindingDescription2EXT{0, sizeof(Vertex),
31+
vk::VertexInputRate::eVertex, 1},
32+
};
33+
34+
constexpr auto vertex_input_v = ShaderVertexInput{
35+
.attributes = vertex_attributes_v,
36+
.bindings = vertex_bindings_v,
37+
};
38+
```
39+
40+
Add `vertex_input_v` to the Shader Create Info:
41+
42+
```cpp
43+
auto const shader_ci = ShaderProgram::CreateInfo{
44+
// ...
45+
.vertex_input = vertex_input_v,
46+
.set_layouts = {},
47+
};
48+
```
49+
50+
With the vertex input defined, we can update the vertex shader and recompile it:
51+
52+
```glsl
53+
#version 450 core
54+
55+
layout (location = 0) in vec2 a_pos;
56+
layout (location = 1) in vec3 a_color;
57+
58+
layout (location = 0) out vec3 out_color;
59+
60+
void main() {
61+
const vec2 position = a_pos;
62+
63+
out_color = a_color;
64+
gl_Position = vec4(position, 0.0, 1.0);
65+
}
66+
```
67+
68+
Add a VBO (Vertex Buffer Object) member and create it:
69+
70+
```cpp
71+
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{
74+
.usage = vk::BufferUsageFlagBits::eVertexBuffer,
75+
.size = 3 * sizeof(Vertex),
76+
.type = vma::BufferType::Host,
77+
};
78+
m_vbo.emplace(m_allocator.get(), buffer_ci);
79+
80+
// vertices that were previously hard-coded in the shader.
81+
static constexpr auto vertices_v = std::array{
82+
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
83+
Vertex{.position = {0.5f, -0.5f}, .color = {0.0f, 1.0f, 0.0f}},
84+
Vertex{.position = {0.0f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}},
85+
};
86+
// 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));
88+
}
89+
```
90+
91+
Bind the VBO before recording the draw call:
92+
93+
```cpp
94+
// single VBO at binding 0 at no offset.
95+
command_buffer.bindVertexBuffers(0, m_vbo->get_raw().buffer,
96+
vk::DeviceSize{});
97+
// m_vbo has 3 vertices.
98+
command_buffer.draw(3, 1, 0, 0);
99+
```
100+
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:
138+
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+
```

src/app.cpp

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <app.hpp>
2+
#include <vertex.hpp>
23
#include <cassert>
34
#include <chrono>
45
#include <fstream>
@@ -11,6 +12,28 @@ namespace lvk {
1112
using namespace std::chrono_literals;
1213

1314
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+
1437
[[nodiscard]] auto locate_assets_dir() -> fs::path {
1538
// look for '<path>/assets/', starting from the working
1639
// directory and walking up the parent directory tree.
@@ -83,6 +106,8 @@ void App::run() {
83106
create_imgui();
84107
create_shader();
85108

109+
create_vertex_buffer();
110+
86111
main_loop();
87112
}
88113

@@ -236,16 +261,49 @@ void App::create_allocator() {
236261
void App::create_shader() {
237262
auto const vertex_spirv = to_spir_v(asset_path("shader.vert"));
238263
auto const fragment_spirv = to_spir_v(asset_path("shader.frag"));
264+
239265
auto const shader_ci = ShaderProgram::CreateInfo{
240266
.device = *m_device,
241267
.vertex_spirv = vertex_spirv,
242268
.fragment_spirv = fragment_spirv,
243-
.vertex_input = {},
269+
.vertex_input = vertex_input_v,
244270
.set_layouts = {},
245271
};
246272
m_shader.emplace(shader_ci);
247273
}
248274

275+
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.
285+
static constexpr auto vertices_v = std::array{
286+
Vertex{.position = {-0.5f, -0.5f}, .color = {1.0f, 0.0f, 0.0f}},
287+
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}},
290+
};
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));
293+
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));
305+
}
306+
249307
auto App::asset_path(std::string_view const uri) const -> fs::path {
250308
return m_assets_dir / uri;
251309
}
@@ -424,7 +482,13 @@ void App::inspect() {
424482

425483
void App::draw(vk::CommandBuffer const command_buffer) const {
426484
m_shader->bind(command_buffer, m_framebuffer_size);
427-
// current shader has hard-coded logic for 3 vertices.
428-
command_buffer.draw(3, 1, 0, 0);
485+
// 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);
429493
}
430494
} // namespace lvk

src/app.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class App {
3838
void create_imgui();
3939
void create_allocator();
4040
void create_shader();
41+
void create_vertex_buffer();
4142

4243
[[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path;
4344

@@ -78,6 +79,9 @@ class App {
7879

7980
std::optional<ShaderProgram> m_shader{};
8081

82+
std::optional<vma::Buffer> m_vbo{};
83+
std::optional<vma::Buffer> m_ibo{};
84+
8185
glm::ivec2 m_framebuffer_size{};
8286
std::optional<RenderTarget> m_render_target{};
8387
bool m_wireframe{};

src/glsl/shader.vert

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,13 @@
11
#version 450 core
22

3+
layout (location = 0) in vec2 a_pos;
4+
layout (location = 1) in vec3 a_color;
5+
36
layout (location = 0) out vec3 out_color;
47

58
void main() {
6-
const vec2 positions[] = {
7-
vec2(-0.5, -0.5),
8-
vec2(0.5, -0.5),
9-
vec2(0.0, 0.5),
10-
};
11-
12-
const vec3 colors[] = {
13-
vec3(1.0, 0.0, 0.0),
14-
vec3(0.0, 1.0, 0.0),
15-
vec3(0.0, 0.0, 1.0),
16-
};
17-
18-
const vec2 position = positions[gl_VertexIndex];
9+
const vec2 position = a_pos;
1910

20-
out_color = colors[gl_VertexIndex];
11+
out_color = a_color;
2112
gl_Position = vec4(position, 0.0, 1.0);
2213
}

src/vertex.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
#include <glm/vec2.hpp>
3+
#include <glm/vec3.hpp>
4+
5+
namespace lvk {
6+
struct Vertex {
7+
glm::vec2 position{};
8+
glm::vec3 color{};
9+
};
10+
} // namespace lvk

0 commit comments

Comments
 (0)