Skip to content

🐛 Fix present semaphore sync #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
name: ci-push
name: ci-pr
on:
push:
pull_request:
branches-ignore:
- staging
workflow_dispatch:
jobs:
format-check:
runs-on: ubuntu-latest
Expand All @@ -25,11 +26,11 @@ jobs:
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: export CXX=g++-14; cmake -S . --preset=default -B build -DGLFW_BUILD_X11=OFF
run: cmake -S . --preset=ninja-gcc -B build -DGLFW_BUILD_X11=OFF -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
- name: build debug
run: cmake --build build --config=Debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
Expand All @@ -43,9 +44,9 @@ jobs:
- name: configure
run: cmake -S . --preset=ninja-clang -B build -DGLFW_BUILD_X11=OFF
- name: build debug
run: cmake --build build --config=Debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
Expand All @@ -57,11 +58,11 @@ jobs:
- name: init
run: uname -m; sudo apt update -yqq && sudo apt install -yqq ninja-build mesa-common-dev libwayland-dev libxkbcommon-dev wayland-protocols extra-cmake-modules
- name: configure
run: export CXX=g++-14; cmake -S . --preset=default -B build -DGLFW_BUILD_X11=OFF
run: cmake -S . --preset=ninja-gcc -B build -DGLFW_BUILD_X11=OFF -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14
- name: build debug
run: cmake --build build --config=Debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
Expand All @@ -75,9 +76,9 @@ jobs:
- name: configure
run: cmake -S . --preset=ninja-clang -B build -DGLFW_BUILD_X11=OFF
- name: build debug
run: cmake --build build --config=Debug
run: cmake --build build --config=Debug -- -v
- name: build release
run: cmake --build build --config=Release
run: cmake --build build --config=Release -- -v
- name: test debug
run: cd build && ctest -V -C Debug
- name: test release
Expand Down Expand Up @@ -105,9 +106,9 @@ jobs:
- name: configure
run: cmake -S . --preset=ninja-clang -B clang
- name: build debug
run: cmake --build clang --config=Debug
run: cmake --build clang --config=Debug -- -v
- name: build release
run: cmake --build clang --config=Release
run: cmake --build clang --config=Release -- -v
- name: test debug
run: cd clang && ctest -V -C Debug
- name: test release
Expand Down
156 changes: 144 additions & 12 deletions CMakePresets.json
Original file line number Diff line number Diff line change
@@ -1,65 +1,131 @@
{
"version": 2,
"version": 5,
"cmakeMinimumRequired": {
"major": 3,
"minor": 20,
"minor": 24,
"patch": 0
},
"configurePresets": [
{
"name": "default",
"description": "Build configuration using Ninja Multi-config",
"displayName": "Default Config",
"description": "Base configuration using Ninja Multi-config",
"generator": "Ninja Multi-Config",
"binaryDir": "${sourceDir}/out/default",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "ninja-clang",
"description": "Build configuration using Ninja Multi-config / clang",
"name": "base-gcc",
"hidden": true,
"inherits": "default",
"cacheVariables": {
"CMAKE_C_COMPILER": "gcc",
"CMAKE_CXX_COMPILER": "g++"
}
},
{
"name": "base-clang",
"hidden": true,
"inherits": "default",
"binaryDir": "${sourceDir}/out/clang",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++"
}
},
{
"name": "base-msvc",
"hidden": true,
"inherits": "default",
"cacheVariables": {
"CMAKE_C_COMPILER": "cl",
"CMAKE_CXX_COMPILER": "cl"
}
},
{
"name": "ninja-gcc",
"displayName": "Ninja GCC",
"description": "Build configuration using Ninja Multi-config / GCC",
"inherits": "base-gcc",
"binaryDir": "${sourceDir}/out/gcc",
"cacheVariables": {
"CMAKE_CXX_FLAGS_DEBUG_INIT": "-Wall -Wextra -Wpedantic -Werror=return-type",
"CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -Wpedantic -Werror"
}
},
{
"name": "ninja-clang",
"displayName": "Ninja Clang",
"description": "Build configuration using Ninja Multi-config / Clang",
"inherits": "base-clang",
"binaryDir": "${sourceDir}/out/clang",
"cacheVariables": {
"CMAKE_CXX_FLAGS_DEBUG_INIT": "-Wall -Wextra -Wpedantic -Werror=return-type",
"CMAKE_CXX_FLAGS_INIT": "-Wall -Wextra -Wpedantic -Werror"
}
},
{
"name": "ninja-msvc",
"displayName": "Ninja MSVC",
"description": "Build configuration using Ninja Multi-config / MSVC",
"inherits": "base-msvc",
"binaryDir": "${sourceDir}/out/msvc",
"cacheVariables": {
"CMAKE_CXX_FLAGS_INIT": "/WX"
}
},
{
"name": "ninja-ubsan",
"displayName": "Ninja UBSan",
"description": "UBSan build configuration using Ninja Multi-config",
"inherits": "default",
"binaryDir": "${sourceDir}/out/ubsan",
"cacheVariables": {
"CMAKE_C_FLAGS": "-fsanitize=undefined",
"CMAKE_CXX_FLAGS": "-fsanitize=undefined"
"CMAKE_CXX_FLAGS_DEBUG_INIT": "-fsanitize=undefined -Wall -Wextra -Wpedantic -Werror=return-type",
"CMAKE_CXX_FLAGS_INIT": "-fsanitize=undefined -Wall -Wextra -Wpedantic -Werror"
}
},
{
"name": "ninja-asan",
"displayName": "Ninja ASan",
"description": "ASan build configuration using Ninja Multi-config",
"inherits": "default",
"binaryDir": "${sourceDir}/out/asan",
"cacheVariables": {
"CMAKE_C_FLAGS": "-fsanitize=address",
"CMAKE_CXX_FLAGS": "-fsanitize=address"
"CMAKE_CXX_FLAGS_DEBUG_INIT": "-fsanitize=address -Wall -Wextra -Wpedantic -Werror=return-type",
"CMAKE_CXX_FLAGS_INIT": "-fsanitize=address -Wall -Wextra -Wpedantic -Werror"
}
},
{
"name": "ninja-msvc-asan",
"displayName": "Ninja MSVC ASan",
"description": "ASan build configuration using Ninja Multi-config",
"inherits": "base-msvc",
"binaryDir": "${sourceDir}/out/asan",
"cacheVariables": {
"CMAKE_CXX_FLAGS_INIT": "-fsanitize=address"
}
},
{
"name": "ninja-tsan",
"displayName": "Ninja TSan",
"description": "TSan build configuration using Ninja Multi-config",
"inherits": "default",
"binaryDir": "${sourceDir}/out/tsan",
"cacheVariables": {
"CMAKE_C_FLAGS": "-fsanitize=thread",
"CMAKE_CXX_FLAGS": "-fsanitize=thread"
"CMAKE_CXX_FLAGS_INIT": "-fsanitize=thread -Wall -Wextra -Wpedantic -Werror=return-type"
}
},
{
"name": "vs22",
"displayName": "Visual Studio 2022",
"description": "Build configuration using Visual Studio 17 (2022)",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/out/vs",
"cacheVariables": {
"CMAKE_CXX_FLAGS_INIT": "/WX"
},
"architecture": {
"value": "x64",
"strategy": "external"
Expand All @@ -82,6 +148,36 @@
"configurePreset": "default",
"configuration": "RelWithDebInfo"
},
{
"name": "GCC Debug",
"configurePreset": "ninja-gcc",
"configuration": "Debug"
},
{
"name": "GCC RelWithDebInfo",
"configurePreset": "ninja-gcc",
"configuration": "RelWithDebInfo"
},
{
"name": "Clang Debug",
"configurePreset": "ninja-clang",
"configuration": "Debug"
},
{
"name": "Clang RelWithDebInfo",
"configurePreset": "ninja-clang",
"configuration": "RelWithDebInfo"
},
{
"name": "MSVC Debug",
"configurePreset": "ninja-msvc",
"configuration": "Debug"
},
{
"name": "MSVC Release",
"configurePreset": "ninja-msvc",
"configuration": "Release"
},
{
"name": "UBSan Debug",
"configurePreset": "ninja-ubsan",
Expand All @@ -107,6 +203,42 @@
"configuration": "RelWithDebInfo",
"inheritConfigureEnvironment": true
},
{
"name": "GCC Debug",
"configurePreset": "ninja-gcc",
"configuration": "Debug",
"inheritConfigureEnvironment": true
},
{
"name": "GCC RelWithDebInfo",
"configurePreset": "ninja-gcc",
"configuration": "RelWithDebInfo",
"inheritConfigureEnvironment": true
},
{
"name": "Clang Debug",
"configurePreset": "ninja-clang",
"configuration": "Debug",
"inheritConfigureEnvironment": true
},
{
"name": "Clang RelWithDebInfo",
"configurePreset": "ninja-clang",
"configuration": "RelWithDebInfo",
"inheritConfigureEnvironment": true
},
{
"name": "MSVC Debug",
"configurePreset": "ninja-msvc",
"configuration": "Debug",
"inheritConfigureEnvironment": true
},
{
"name": "MSVC Release",
"configurePreset": "ninja-msvc",
"configuration": "Release",
"inheritConfigureEnvironment": true
},
{
"name": "UBSan Debug",
"configurePreset": "ninja-ubsan",
Expand Down
3 changes: 0 additions & 3 deletions guide/src/rendering/render_sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ Add a private `struct RenderSync` to `App`:
struct RenderSync {
// signaled when Swapchain image has been acquired.
vk::UniqueSemaphore draw{};
// signaled when image is ready to be presented.
vk::UniqueSemaphore present{};
// signaled with present Semaphore, waited on before next render.
vk::UniqueFence drawn{};
// used to record rendering commands.
Expand Down Expand Up @@ -68,7 +66,6 @@ void App::create_render_sync() {
std::views::zip(m_render_sync, command_buffers)) {
sync.command_buffer = command_buffer;
sync.draw = m_device->createSemaphoreUnique({});
sync.present = m_device->createSemaphoreUnique({});
sync.drawn = m_device->createFenceUnique(fence_create_info_v);
}
}
Expand Down
4 changes: 3 additions & 1 deletion guide/src/rendering/swapchain_loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ Additionally, the number of swapchain images can vary, whereas the engine should

## Virtual Frames

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.
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.

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.

## Image Layouts

Expand Down
42 changes: 40 additions & 2 deletions guide/src/rendering/swapchain_update.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
# Swapchain Update

Add a vector of semaphores and populate them in `recreate()`:

```cpp
void create_present_semaphores();

// ...
// signaled when image is ready to be presented.
std::vector<vk::UniqueSemaphore> m_present_semaphores{};

// ...
auto Swapchain::recreate(glm::ivec2 size) -> bool {
// ...
populate_images();
create_image_views();
// recreate present semaphores as the image count might have changed.
create_present_semaphores();
// ...
}

void Swapchain::create_present_semaphores() {
m_present_semaphores.clear();
m_present_semaphores.resize(m_images.size());
for (auto& semaphore : m_present_semaphores) {
semaphore = m_device.createSemaphoreUnique({});
}
}
```

Add a function to get the present semaphore corresponding to the acquired image, this will be signaled by render command submission:

```cpp
auto Swapchain::get_present_semaphore() const -> vk::Semaphore {
return *m_present_semaphores.at(m_image_index.value());
}
```

Swapchain acquire/present operations can have various results. We constrain ourselves to the following:

- `eSuccess`: all good
Expand Down Expand Up @@ -59,13 +95,15 @@ auto Swapchain::acquire_next_image(vk::Semaphore const to_signal)
Similarly, present:

```cpp
auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait)
auto Swapchain::present(vk::Queue const queue)
-> bool {
auto const image_index = static_cast<std::uint32_t>(m_image_index.value());
auto const wait_semaphore =
*m_present_semaphores.at(static_cast<std::size_t>(image_index));
auto present_info = vk::PresentInfoKHR{};
present_info.setSwapchains(*m_swapchain)
.setImageIndices(image_index)
.setWaitSemaphores(to_wait);
.setWaitSemaphores(wait_semaphore);
// avoid VulkanHPP ErrorOutOfDateKHR exceptions by using alternate API.
auto const result = queue.presentKHR(&present_info);
m_image_index.reset();
Expand Down
Loading
Loading