Skip to content

Commit f713da0

Browse files
committed
🐛 Fix present semaphore sync
Discovered on SDK 1.4.313
1 parent 3114065 commit f713da0

File tree

8 files changed

+71
-19
lines changed

8 files changed

+71
-19
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: ci-push
1+
name: ci-pr
22
on:
33
pull_request:
44
branches-ignore:

guide/src/rendering/render_sync.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ Add a private `struct RenderSync` to `App`:
1717
struct RenderSync {
1818
// signaled when Swapchain image has been acquired.
1919
vk::UniqueSemaphore draw{};
20-
// signaled when image is ready to be presented.
21-
vk::UniqueSemaphore present{};
2220
// signaled with present Semaphore, waited on before next render.
2321
vk::UniqueFence drawn{};
2422
// used to record rendering commands.
@@ -68,7 +66,6 @@ void App::create_render_sync() {
6866
std::views::zip(m_render_sync, command_buffers)) {
6967
sync.command_buffer = command_buffer;
7068
sync.draw = m_device->createSemaphoreUnique({});
71-
sync.present = m_device->createSemaphoreUnique({});
7269
sync.drawn = m_device->createFenceUnique(fence_create_info_v);
7370
}
7471
}

guide/src/rendering/swapchain_loop.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ Additionally, the number of swapchain images can vary, whereas the engine should
1818

1919
## Virtual Frames
2020

21-
All the dynamic resources used during the rendering of a frame comprise a virtual frame. The application has a fixed number of virtual frames which it cycles through on each render pass. Each frame will be associated with a `vk::Fence` which will be waited on before rendering to it again. It will also have a pair of `vk::Semaphore`s to synchronize the acquire, render, and present calls on the GPU (we don't need to wait for them in the code). Lastly, there will be a Command Buffer per virtual frame, where all rendering commands for that frame (including layout transitions) will be recorded.
21+
All the dynamic resources used during the rendering of a frame comprise a virtual frame. The application has a fixed number of virtual frames which it cycles through on each render pass. Each frame will be associated with a `vk::Fence` which will be waited on before rendering to it again. It will also have a `vk::Semaphore` to synchronize the acquire and render calls on the GPU (we don't need to wait for them in the code). Lastly, there will be a Command Buffer per virtual frame, where all rendering commands for that frame (including layout transitions) will be recorded.
22+
23+
Presentation will require a semaphore for synchronization too, but since the swapchain loop waits on the _drawn_ fence, which will be pre-signaled for each virtual frame on first use, present semaphores cannot be part of the virtual frame. It is possible to acquire another image and submit commands with a present semaphore that has not been signaled yet - this is invalid. Thus, these semaphores will be tied to the swapchain images (associated with their indices), and recreated with it.
2224

2325
## Image Layouts
2426

guide/src/rendering/swapchain_update.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
# Swapchain Update
22

3+
Add a vector of semaphores and populate them in `recreate()`:
4+
5+
```cpp
6+
void create_present_semaphores();
7+
8+
// ...
9+
// signaled when image is ready to be presented.
10+
std::vector<vk::UniqueSemaphore> m_present_semaphores{};
11+
12+
// ...
13+
auto Swapchain::recreate(glm::ivec2 size) -> bool {
14+
// ...
15+
populate_images();
16+
create_image_views();
17+
// recreate present semaphores as the image count might have changed.
18+
create_present_semaphores();
19+
// ...
20+
}
21+
22+
void Swapchain::create_present_semaphores() {
23+
m_present_semaphores.clear();
24+
m_present_semaphores.resize(m_images.size());
25+
for (auto& semaphore : m_present_semaphores) {
26+
semaphore = m_device.createSemaphoreUnique({});
27+
}
28+
}
29+
```
30+
31+
Add a function to get the present semaphore corresponding to the acquired image, this will be signaled by render command submission:
32+
33+
```cpp
34+
auto Swapchain::get_present_semaphore() const -> vk::Semaphore {
35+
return *m_present_semaphores.at(m_image_index.value());
36+
}
37+
```
38+
339
Swapchain acquire/present operations can have various results. We constrain ourselves to the following:
440

541
- `eSuccess`: all good
@@ -59,13 +95,15 @@ auto Swapchain::acquire_next_image(vk::Semaphore const to_signal)
5995
Similarly, present:
6096
6197
```cpp
62-
auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait)
98+
auto Swapchain::present(vk::Queue const queue)
6399
-> bool {
64100
auto const image_index = static_cast<std::uint32_t>(m_image_index.value());
101+
auto const wait_semaphore =
102+
*m_present_semaphores.at(static_cast<std::size_t>(image_index));
65103
auto present_info = vk::PresentInfoKHR{};
66104
present_info.setSwapchains(*m_swapchain)
67105
.setImageIndices(image_index)
68-
.setWaitSemaphores(to_wait);
106+
.setWaitSemaphores(wait_semaphore);
69107
// avoid VulkanHPP ErrorOutOfDateKHR exceptions by using alternate API.
70108
auto const result = queue.presentKHR(&present_info);
71109
m_image_index.reset();

src/app.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ void App::create_render_sync() {
132132
std::views::zip(m_render_sync, command_buffers)) {
133133
sync.command_buffer = command_buffer;
134134
sync.draw = m_device->createSemaphoreUnique({});
135-
sync.present = m_device->createSemaphoreUnique({});
136135
sync.drawn = m_device->createFenceUnique(fence_create_info_v);
137136
}
138137
}
@@ -256,7 +255,7 @@ void App::submit_and_present() {
256255
wait_semaphore_info.setSemaphore(*render_sync.draw)
257256
.setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput);
258257
auto signal_semaphore_info = vk::SemaphoreSubmitInfo{};
259-
signal_semaphore_info.setSemaphore(*render_sync.present)
258+
signal_semaphore_info.setSemaphore(m_swapchain->get_present_semaphore())
260259
.setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput);
261260
submit_info.setCommandBufferInfos(command_buffer_info)
262261
.setWaitSemaphoreInfos(wait_semaphore_info)
@@ -270,8 +269,7 @@ void App::submit_and_present() {
270269
// framebuffer size does not match the Swapchain image size, check it
271270
// explicitly.
272271
auto const fb_size_changed = m_framebuffer_size != m_swapchain->get_size();
273-
auto const out_of_date =
274-
!m_swapchain->present(m_queue, *render_sync.present);
272+
auto const out_of_date = !m_swapchain->present(m_queue);
275273
if (fb_size_changed || out_of_date) {
276274
m_swapchain->recreate(m_framebuffer_size);
277275
}

src/app.hpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ class App {
1313

1414
private:
1515
struct RenderSync {
16-
// signalled when Swapchain image has been acquired.
16+
// signaled when Swapchain image has been acquired.
1717
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.
18+
// signaled with present Semaphore, waited on before next render.
2119
vk::UniqueFence drawn{};
2220
// used to record rendering commands.
2321
vk::CommandBuffer command_buffer{};

src/swapchain.cpp

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ auto Swapchain::recreate(glm::ivec2 size) -> bool {
120120

121121
populate_images();
122122
create_image_views();
123+
// recreate present semaphores as the image count might have changed.
124+
create_present_semaphores();
123125

124126
size = get_size();
125127
std::println("[lvk] Swapchain [{}x{}]", size.x, size.y);
@@ -155,13 +157,18 @@ auto Swapchain::base_barrier() const -> vk::ImageMemoryBarrier2 {
155157
return ret;
156158
}
157159

158-
auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait)
159-
-> bool {
160+
auto Swapchain::get_present_semaphore() const -> vk::Semaphore {
161+
return *m_present_semaphores.at(m_image_index.value());
162+
}
163+
164+
auto Swapchain::present(vk::Queue const queue) -> bool {
160165
auto const image_index = static_cast<std::uint32_t>(m_image_index.value());
166+
auto const wait_semaphore =
167+
*m_present_semaphores.at(static_cast<std::size_t>(image_index));
161168
auto present_info = vk::PresentInfoKHR{};
162169
present_info.setSwapchains(*m_swapchain)
163170
.setImageIndices(image_index)
164-
.setWaitSemaphores(to_wait);
171+
.setWaitSemaphores(wait_semaphore);
165172
// avoid VulkanHPP ErrorOutOfDateKHR exceptions by using alternate API.
166173
auto const result = queue.presentKHR(&present_info);
167174
m_image_index.reset();
@@ -195,4 +202,12 @@ void Swapchain::create_image_views() {
195202
m_image_views.push_back(m_device.createImageViewUnique(image_view_ci));
196203
}
197204
}
205+
206+
void Swapchain::create_present_semaphores() {
207+
m_present_semaphores.clear();
208+
m_present_semaphores.resize(m_images.size());
209+
for (auto& semaphore : m_present_semaphores) {
210+
semaphore = m_device.createSemaphoreUnique({});
211+
}
212+
}
198213
} // namespace lvk

src/swapchain.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ class Swapchain {
2222

2323
[[nodiscard]] auto base_barrier() const -> vk::ImageMemoryBarrier2;
2424

25-
[[nodiscard]] auto present(vk::Queue queue, vk::Semaphore to_wait) -> bool;
25+
[[nodiscard]] auto get_present_semaphore() const -> vk::Semaphore;
26+
[[nodiscard]] auto present(vk::Queue queue) -> bool;
2627

2728
private:
2829
void populate_images();
2930
void create_image_views();
31+
void create_present_semaphores();
3032

3133
vk::Device m_device{};
3234
Gpu m_gpu{};
@@ -35,6 +37,8 @@ class Swapchain {
3537
vk::UniqueSwapchainKHR m_swapchain{};
3638
std::vector<vk::Image> m_images{};
3739
std::vector<vk::UniqueImageView> m_image_views{};
40+
// signaled when image is ready to be presented.
41+
std::vector<vk::UniqueSemaphore> m_present_semaphores{};
3842
std::optional<std::size_t> m_image_index{};
3943
};
4044
} // namespace lvk

0 commit comments

Comments
 (0)