Skip to content

Commit 712d8ab

Browse files
committed
RenderSync
1 parent 10c6019 commit 712d8ab

File tree

4 files changed

+140
-5
lines changed

4 files changed

+140
-5
lines changed

guide/src/rendering/render_sync.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,75 @@
11
# Render Sync
2+
3+
Create a new header `resource_buffering.hpp`:
4+
5+
```cpp
6+
// Number of virtual frames.
7+
inline constexpr std::size_t buffering_v{2};
8+
9+
// Alias for N-buffered resources.
10+
template <typename Type>
11+
using Buffered = std::array<Type, buffering_v>;
12+
```
13+
14+
Add a private `struct RenderSync` to `App`:
15+
16+
```cpp
17+
struct RenderSync {
18+
// signaled when Swapchain image has been acquired.
19+
vk::UniqueSemaphore draw{};
20+
// signaled when image is ready to be presented.
21+
vk::UniqueSemaphore present{};
22+
// signaled with present Semaphore, waited on before next render.
23+
vk::UniqueFence drawn{};
24+
// used to record rendering commands.
25+
vk::CommandBuffer command_buffer{};
26+
};
27+
```
28+
29+
Add the new members associated with the Swapchain loop:
30+
31+
```cpp
32+
// command pool for all render Command Buffers.
33+
vk::UniqueCommandPool m_render_cmd_pool{};
34+
// Sync and Command Buffer for virtual frames.
35+
Buffered<RenderSync> m_render_sync{};
36+
// Current virtual frame index.
37+
std::size_t m_frame_index{};
38+
```
39+
40+
Add, implement, and call the create function:
41+
42+
```cpp
43+
void App::create_render_sync() {
44+
// Command Buffers are 'allocated' from a Command Pool (which is 'created'
45+
// like all other Vulkan objects so far). We can allocate all the buffers
46+
// from a single pool here.
47+
auto command_pool_ci = vk::CommandPoolCreateInfo{};
48+
// this flag enables resetting the command buffer for re-recording (unlike a
49+
// single-time submit scenario).
50+
command_pool_ci.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer)
51+
.setQueueFamilyIndex(m_gpu.queue_family);
52+
m_render_cmd_pool = m_device->createCommandPoolUnique(command_pool_ci);
53+
54+
auto command_buffer_ai = vk::CommandBufferAllocateInfo{};
55+
command_buffer_ai.setCommandPool(*m_render_cmd_pool)
56+
.setCommandBufferCount(static_cast<std::uint32_t>(resource_buffering_v))
57+
.setLevel(vk::CommandBufferLevel::ePrimary);
58+
auto const command_buffers =
59+
m_device->allocateCommandBuffers(command_buffer_ai);
60+
assert(command_buffers.size() == m_render_sync.size());
61+
62+
// we create Render Fences as pre-signaled so that on the first render for
63+
// each virtual frame we don't wait on their fences (since there's nothing
64+
// to wait for yet).
65+
static constexpr auto fence_create_info_v =
66+
vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled};
67+
for (auto [sync, command_buffer] :
68+
std::views::zip(m_render_sync, command_buffers)) {
69+
sync.command_buffer = command_buffer;
70+
sync.draw = m_device->createSemaphoreUnique({});
71+
sync.present = m_device->createSemaphoreUnique({});
72+
sync.drawn = m_device->createFenceUnique(fence_create_info_v);
73+
}
74+
}
75+
```

src/app.cpp

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#include <app.hpp>
2+
#include <cassert>
23
#include <print>
3-
4-
#include <thread>
4+
#include <ranges>
55

66
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
77

@@ -13,6 +13,7 @@ void App::run() {
1313
select_gpu();
1414
create_device();
1515
create_swapchain();
16+
create_render_sync();
1617

1718
main_loop();
1819
}
@@ -90,12 +91,42 @@ void App::create_swapchain() {
9091
m_swapchain.emplace(*m_device, m_gpu, *m_surface, size);
9192
}
9293

94+
void App::create_render_sync() {
95+
// Command Buffers are 'allocated' from a Command Pool (which is 'created'
96+
// like all other Vulkan objects so far). We can allocate all the buffers
97+
// from a single pool here.
98+
auto command_pool_ci = vk::CommandPoolCreateInfo{};
99+
// this flag enables resetting the command buffer for re-recording (unlike a
100+
// single-time submit scenario).
101+
command_pool_ci.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer)
102+
.setQueueFamilyIndex(m_gpu.queue_family);
103+
m_render_cmd_pool = m_device->createCommandPoolUnique(command_pool_ci);
104+
105+
auto command_buffer_ai = vk::CommandBufferAllocateInfo{};
106+
command_buffer_ai.setCommandPool(*m_render_cmd_pool)
107+
.setCommandBufferCount(static_cast<std::uint32_t>(resource_buffering_v))
108+
.setLevel(vk::CommandBufferLevel::ePrimary);
109+
auto const command_buffers =
110+
m_device->allocateCommandBuffers(command_buffer_ai);
111+
assert(command_buffers.size() == m_render_sync.size());
112+
113+
// we create Render Fences as pre-signaled so that on the first render for
114+
// each virtual frame we don't wait on their fences (since there's nothing
115+
// to wait for yet).
116+
static constexpr auto fence_create_info_v =
117+
vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled};
118+
for (auto [sync, command_buffer] :
119+
std::views::zip(m_render_sync, command_buffers)) {
120+
sync.command_buffer = command_buffer;
121+
sync.draw = m_device->createSemaphoreUnique({});
122+
sync.present = m_device->createSemaphoreUnique({});
123+
sync.drawn = m_device->createFenceUnique(fence_create_info_v);
124+
}
125+
}
126+
93127
void App::main_loop() {
94-
auto count = 0;
95128
while (glfwWindowShouldClose(m_window.get()) == GLFW_FALSE) {
96129
glfwPollEvents();
97-
if (++count > 500) { break; }
98-
std::this_thread::sleep_for(std::chrono::milliseconds{10});
99130
}
100131
}
101132
} // namespace lvk

src/app.hpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include <gpu.hpp>
3+
#include <resource_buffering.hpp>
34
#include <scoped_waiter.hpp>
45
#include <swapchain.hpp>
56
#include <vulkan/vulkan.hpp>
@@ -11,12 +12,24 @@ class App {
1112
void run();
1213

1314
private:
15+
struct RenderSync {
16+
// signalled when Swapchain image has been acquired.
17+
vk::UniqueSemaphore draw{};
18+
// signalled when image is ready to be presented.
19+
vk::UniqueSemaphore present{};
20+
// signalled with present Semaphore, waited on before next render.
21+
vk::UniqueFence drawn{};
22+
// used to record rendering commands.
23+
vk::CommandBuffer command_buffer{};
24+
};
25+
1426
void create_window();
1527
void create_instance();
1628
void create_surface();
1729
void select_gpu();
1830
void create_device();
1931
void create_swapchain();
32+
void create_render_sync();
2033

2134
void main_loop();
2235

@@ -28,6 +41,12 @@ class App {
2841
vk::Queue m_queue{};
2942

3043
std::optional<Swapchain> m_swapchain{};
44+
// command pool for all render Command Buffers.
45+
vk::UniqueCommandPool m_render_cmd_pool{};
46+
// Sync and Command Buffer for virtual frames.
47+
Buffered<RenderSync> m_render_sync{};
48+
// Current virtual frame index.
49+
std::size_t m_frame_index{};
3150

3251
ScopedWaiter m_waiter{};
3352
};

src/resource_buffering.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#pragma once
2+
#include <array>
3+
4+
namespace lvk {
5+
// Number of virtual frames.
6+
inline constexpr std::size_t resource_buffering_v{2};
7+
8+
// Alias for N-buffered resources.
9+
template <typename Type>
10+
using Buffered = std::array<Type, resource_buffering_v>;
11+
} // namespace lvk

0 commit comments

Comments
 (0)