Skip to content

Commit 9ccd0d9

Browse files
committed
Pipeline Layout
1 parent 811b904 commit 9ccd0d9

File tree

5 files changed

+150
-2
lines changed

5 files changed

+150
-2
lines changed

guide/src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,5 @@
4343
- [Command Block](memory/command_block.md)
4444
- [Device Buffers](memory/device_buffers.md)
4545
- [Images](memory/images.md)
46+
- [Descriptor Sets](descriptor_sets/README.md)
47+
- [Pipeline Layout](descriptor_sets/pipeline_layout.md)

guide/src/descriptor_sets/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Descriptor Sets
2+
3+
[Vulkan Descriptor](https://docs.vulkan.org/guide/latest/mapping_data_to_shaders.html#descriptors)s are essentially typed pointers to resources that shaders can use, eg uniform/storage buffers or combined image samplers (textures with samplers). A Descriptor Set is a collection of descriptors at various **bindings** that is bound together as an atomic unit. Shaders can declare input based on these set and binding numbers, and any sets the shader uses must have been updated and bound before drawing. A Descriptor Set Layout is a description of a collection of descriptor sets associated with a particular set number, usually describing all the sets in a shader. Descriptor sets are allocated using a Descriptor Pool and the desired set layout(s).
4+
5+
Structuring set layouts and managing descriptor sets are complex topics with many viable approaches, each with their pros and cons. Some robust ones are described in this [page](https://docs.vulkan.org/samples/latest/samples/performance/descriptor_management/README.html). 2D frameworks - and even simple/basic 3D ones - can simply allocate and update sets every frame, as described in the docs as the "simplest approach". Here's an [extremely detailed](https://zeux.io/2020/02/27/writing-an-efficient-vulkan-renderer/) - albeit a bit dated now - post by Arseny on the subject. A more modern approach, namely "bindless" or Descriptor Indexing, is described in the official docs [here](https://docs.vulkan.org/samples/latest/samples/extensions/descriptor_indexing/README.html).
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Pipeline Layout
2+
3+
A [Vulkan Pipeline Layout](https://registry.khronos.org/vulkan/specs/latest/man/html/VkPipelineLayout.html) represents a sequence of descriptor sets (and push constants) associated with a shader program. Even when using Shader Objects, a Pipeline Layout is needed to utilize descriptor sets.
4+
5+
Starting with the layout of a single descriptor set containing a uniform buffer to set the view/projection matrices in, store a descriptor pool in `App` and create it before the shader:
6+
7+
```cpp
8+
vk::UniqueDescriptorPool m_descriptor_pool{};
9+
10+
// ...
11+
void App::create_descriptor_pool() {
12+
static constexpr auto pool_sizes_v = std::array{
13+
// 2 uniform buffers, can be more if desired.
14+
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 2},
15+
};
16+
auto pool_ci = vk::DescriptorPoolCreateInfo{};
17+
// allow 16 sets to be allocated from this pool.
18+
pool_ci.setPoolSizes(pool_sizes_v).setMaxSets(16);
19+
m_descriptor_pool = m_device->createDescriptorPoolUnique(pool_ci);
20+
}
21+
```
22+
23+
Add new members to `App` to store the set layouts and pipeline layout. `m_set_layout_views` is just a copy of the descriptor set layout handles in a contiguous vector:
24+
25+
```cpp
26+
std::vector<vk::UniqueDescriptorSetLayout> m_set_layouts{};
27+
std::vector<vk::DescriptorSetLayout> m_set_layout_views{};
28+
vk::UniquePipelineLayout m_pipeline_layout{};
29+
30+
// ...
31+
constexpr auto layout_binding(std::uint32_t binding,
32+
vk::DescriptorType const type) {
33+
return vk::DescriptorSetLayoutBinding{
34+
binding, type, 1, vk::ShaderStageFlagBits::eAllGraphics};
35+
}
36+
37+
// ...
38+
void App::create_pipeline_layout() {
39+
static constexpr auto set_0_bindings_v = std::array{
40+
layout_binding(0, vk::DescriptorType::eUniformBuffer),
41+
};
42+
auto set_layout_cis = std::array<vk::DescriptorSetLayoutCreateInfo, 1>{};
43+
set_layout_cis[0].setBindings(set_0_bindings_v);
44+
45+
for (auto const& set_layout_ci : set_layout_cis) {
46+
m_set_layouts.push_back(
47+
m_device->createDescriptorSetLayoutUnique(set_layout_ci));
48+
m_set_layout_views.push_back(*m_set_layouts.back());
49+
}
50+
51+
auto pipeline_layout_ci = vk::PipelineLayoutCreateInfo{};
52+
pipeline_layout_ci.setSetLayouts(m_set_layout_views);
53+
m_pipeline_layout =
54+
m_device->createPipelineLayoutUnique(pipeline_layout_ci);
55+
}
56+
```
57+
58+
Add a helper function that allocates a set of descriptor sets for the entire layout:
59+
60+
```cpp
61+
auto App::allocate_sets() const -> std::vector<vk::DescriptorSet> {
62+
auto allocate_info = vk::DescriptorSetAllocateInfo{};
63+
allocate_info.setDescriptorPool(*m_descriptor_pool)
64+
.setSetLayouts(m_set_layout_views);
65+
return m_device->allocateDescriptorSets(allocate_info);
66+
}
67+
```
68+
69+
Store a Buffered copy of descriptor sets for one drawable object:
70+
71+
```cpp
72+
Buffered<std::vector<vk::DescriptorSet>> m_descriptor_sets{};
73+
74+
// ...
75+
76+
void App::create_descriptor_sets() {
77+
for (auto& descriptor_sets : m_descriptor_sets) {
78+
descriptor_sets = allocate_sets();
79+
}
80+
}
81+
```

src/app.cpp

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ template <typename T>
1818
return std::bit_cast<std::array<std::byte, sizeof(T)>>(t);
1919
}
2020

21+
constexpr auto layout_binding(std::uint32_t binding,
22+
vk::DescriptorType const type) {
23+
return vk::DescriptorSetLayoutBinding{
24+
binding, type, 1, vk::ShaderStageFlagBits::eAllGraphics};
25+
}
26+
2127
[[nodiscard]] auto locate_assets_dir() -> fs::path {
2228
// look for '<path>/assets/', starting from the working
2329
// directory and walking up the parent directory tree.
@@ -88,10 +94,12 @@ void App::run() {
8894
create_swapchain();
8995
create_render_sync();
9096
create_imgui();
97+
create_descriptor_pool();
98+
create_pipeline_layout();
9199
create_shader();
92100
create_cmd_block_pool();
93101

94-
create_vertex_buffer();
102+
create_descriptor_sets();
95103

96104
main_loop();
97105
}
@@ -243,6 +251,36 @@ void App::create_allocator() {
243251
m_allocator = vma::create_allocator(*m_instance, m_gpu.device, *m_device);
244252
}
245253

254+
void App::create_descriptor_pool() {
255+
static constexpr auto pool_sizes_v = std::array{
256+
// 2 uniform buffers, can be more if desired.
257+
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 2},
258+
};
259+
auto pool_ci = vk::DescriptorPoolCreateInfo{};
260+
// allow 16 sets to be allocated from this pool.
261+
pool_ci.setPoolSizes(pool_sizes_v).setMaxSets(16);
262+
m_descriptor_pool = m_device->createDescriptorPoolUnique(pool_ci);
263+
}
264+
265+
void App::create_pipeline_layout() {
266+
static constexpr auto set_0_bindings_v = std::array{
267+
layout_binding(0, vk::DescriptorType::eUniformBuffer),
268+
};
269+
auto set_layout_cis = std::array<vk::DescriptorSetLayoutCreateInfo, 1>{};
270+
set_layout_cis[0].setBindings(set_0_bindings_v);
271+
272+
for (auto const& set_layout_ci : set_layout_cis) {
273+
m_set_layouts.push_back(
274+
m_device->createDescriptorSetLayoutUnique(set_layout_ci));
275+
m_set_layout_views.push_back(*m_set_layouts.back());
276+
}
277+
278+
auto pipeline_layout_ci = vk::PipelineLayoutCreateInfo{};
279+
pipeline_layout_ci.setSetLayouts(m_set_layout_views);
280+
m_pipeline_layout =
281+
m_device->createPipelineLayoutUnique(pipeline_layout_ci);
282+
}
283+
246284
void App::create_shader() {
247285
auto const vertex_spirv = to_spir_v(asset_path("shader.vert"));
248286
auto const fragment_spirv = to_spir_v(asset_path("shader.frag"));
@@ -298,6 +336,12 @@ void App::create_vertex_buffer() {
298336
};
299337
m_vbo = vma::create_device_buffer(buffer_ci, create_command_block(),
300338
total_bytes_v);
339+
340+
341+
void App::create_descriptor_sets() {
342+
for (auto& descriptor_sets : m_descriptor_sets) {
343+
descriptor_sets = allocate_sets();
344+
}
301345
}
302346

303347
auto App::asset_path(std::string_view const uri) const -> fs::path {
@@ -308,6 +352,13 @@ auto App::create_command_block() const -> CommandBlock {
308352
return CommandBlock{*m_device, m_queue, *m_cmd_block_pool};
309353
}
310354

355+
auto App::allocate_sets() const -> std::vector<vk::DescriptorSet> {
356+
auto allocate_info = vk::DescriptorSetAllocateInfo{};
357+
allocate_info.setDescriptorPool(*m_descriptor_pool)
358+
.setSetLayouts(m_set_layout_views);
359+
return m_device->allocateDescriptorSets(allocate_info);
360+
}
361+
311362
void App::main_loop() {
312363
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
313364
glfwPollEvents();

src/app.hpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ class App {
3838
void create_render_sync();
3939
void create_imgui();
4040
void create_allocator();
41+
void create_descriptor_pool();
42+
void create_pipeline_layout();
4143
void create_shader();
4244
void create_cmd_block_pool();
43-
void create_vertex_buffer();
45+
void create_descriptor_sets();
4446

4547
[[nodiscard]] auto asset_path(std::string_view uri) const -> fs::path;
4648
[[nodiscard]] auto create_command_block() const -> CommandBlock;
49+
[[nodiscard]] auto allocate_sets() const -> std::vector<vk::DescriptorSet>;
4750

4851
void main_loop();
4952

@@ -82,9 +85,15 @@ class App {
8285

8386
std::optional<DearImGui> m_imgui{};
8487

88+
vk::UniqueDescriptorPool m_descriptor_pool{};
89+
std::vector<vk::UniqueDescriptorSetLayout> m_set_layouts{};
90+
std::vector<vk::DescriptorSetLayout> m_set_layout_views{};
91+
vk::UniquePipelineLayout m_pipeline_layout{};
92+
8593
std::optional<ShaderProgram> m_shader{};
8694

8795
vma::Buffer m_vbo{};
96+
Buffered<std::vector<vk::DescriptorSet>> m_descriptor_sets{};
8897

8998
glm::ivec2 m_framebuffer_size{};
9099
std::optional<RenderTarget> m_render_target{};

0 commit comments

Comments
 (0)