diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f3da2f..dbd67a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: branches-ignore: - staging - production + workflow_dispatch: jobs: format-check: runs-on: ubuntu-latest @@ -26,11 +27,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 @@ -44,9 +45,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 @@ -58,11 +59,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 @@ -76,9 +77,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 @@ -106,9 +107,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 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 23c2655..4b0afa6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,7 @@ on: branches: - production - staging + workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest diff --git a/.github/workflows/guide.yml b/.github/workflows/guide.yml index 0400187..c8c2033 100644 --- a/.github/workflows/guide.yml +++ b/.github/workflows/guide.yml @@ -3,6 +3,7 @@ on: pull_request: branches-ignore: - staging + workflow_dispatch: jobs: build-book: runs-on: ubuntu-latest diff --git a/CMakePresets.json b/CMakePresets.json index 8ddf102..a56321d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,14 +1,15 @@ { - "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": { @@ -16,50 +17,115 @@ } }, { - "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" @@ -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", @@ -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", diff --git a/guide/src/rendering/dynamic_rendering.md b/guide/src/rendering/dynamic_rendering.md index b913b64..1627e92 100644 --- a/guide/src/rendering/dynamic_rendering.md +++ b/guide/src/rendering/dynamic_rendering.md @@ -161,7 +161,7 @@ void App::submit_and_present() { wait_semaphore_info.setSemaphore(*render_sync.draw) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); auto signal_semaphore_info = vk::SemaphoreSubmitInfo{}; - signal_semaphore_info.setSemaphore(*render_sync.present) + signal_semaphore_info.setSemaphore(m_swapchain->get_present_semaphore()) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); submit_info.setCommandBufferInfos(command_buffer_info) .setWaitSemaphoreInfos(wait_semaphore_info) @@ -175,8 +175,7 @@ void App::submit_and_present() { // framebuffer size does not match the Swapchain image size, check it // explicitly. auto const fb_size_changed = m_framebuffer_size != m_swapchain->get_size(); - auto const out_of_date = - !m_swapchain->present(m_queue, *render_sync.present); + auto const out_of_date = !m_swapchain->present(m_queue); if (fb_size_changed || out_of_date) { m_swapchain->recreate(m_framebuffer_size); } diff --git a/guide/src/rendering/render_sync.md b/guide/src/rendering/render_sync.md index f18023d..d713863 100644 --- a/guide/src/rendering/render_sync.md +++ b/guide/src/rendering/render_sync.md @@ -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. @@ -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); } } diff --git a/guide/src/rendering/swapchain_loop.md b/guide/src/rendering/swapchain_loop.md index 64f7043..b540525 100644 --- a/guide/src/rendering/swapchain_loop.md +++ b/guide/src/rendering/swapchain_loop.md @@ -19,7 +19,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. For synchronization, each frame will be associated with a [`vk::Fence`](https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-fences) which will be waited on before rendering to it again. It will also have a pair of [`vk::Semaphore`](https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-semaphores)s to synchronize the acquire, render, and present calls on the GPU (we don't need to wait for them on the CPU side / in C++). For recording commands, there will be a [`vk::CommandBuffer`](https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html) 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. For synchronization, each frame will be associated with a [`vk::Fence`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkFence.html) which will be waited on before rendering to it again. It will also have a [`vk::Semaphore`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSemaphore.html) to synchronize the acquire and render calls on the GPU (we don't need to wait for them in the code). For recording commands, there will be a [`vk::CommandBuffer`](https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html) 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 diff --git a/guide/src/rendering/swapchain_update.md b/guide/src/rendering/swapchain_update.md index 20191d9..5ecf7cf 100644 --- a/guide/src/rendering/swapchain_update.md +++ b/guide/src/rendering/swapchain_update.md @@ -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 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 @@ -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(m_image_index.value()); + auto const wait_semaphore = + *m_present_semaphores.at(static_cast(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(); diff --git a/guide/translations/ko-KR/src/rendering/dynamic_rendering.md b/guide/translations/ko-KR/src/rendering/dynamic_rendering.md index e7e5be3..8ccc18f 100644 --- a/guide/translations/ko-KR/src/rendering/dynamic_rendering.md +++ b/guide/translations/ko-KR/src/rendering/dynamic_rendering.md @@ -161,7 +161,7 @@ void App::submit_and_present() { wait_semaphore_info.setSemaphore(*render_sync.draw) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); auto signal_semaphore_info = vk::SemaphoreSubmitInfo{}; - signal_semaphore_info.setSemaphore(*render_sync.present) + signal_semaphore_info.setSemaphore(m_swapchain->get_present_semaphore()) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); submit_info.setCommandBufferInfos(command_buffer_info) .setWaitSemaphoreInfos(wait_semaphore_info) @@ -175,8 +175,7 @@ void App::submit_and_present() { // framebuffer size does not match the Swapchain image size, check it // explicitly. auto const fb_size_changed = m_framebuffer_size != m_swapchain->get_size(); - auto const out_of_date = - !m_swapchain->present(m_queue, *render_sync.present); + auto const out_of_date = !m_swapchain->present(m_queue); if (fb_size_changed || out_of_date) { m_swapchain->recreate(m_framebuffer_size); } diff --git a/guide/translations/ko-KR/src/rendering/render_sync.md b/guide/translations/ko-KR/src/rendering/render_sync.md index 46520ef..ff92e9e 100644 --- a/guide/translations/ko-KR/src/rendering/render_sync.md +++ b/guide/translations/ko-KR/src/rendering/render_sync.md @@ -17,8 +17,6 @@ using Buffered = std::array; 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. @@ -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); } } diff --git a/guide/translations/ko-KR/src/rendering/swapchain_loop.md b/guide/translations/ko-KR/src/rendering/swapchain_loop.md index c506322..3a35fae 100644 --- a/guide/translations/ko-KR/src/rendering/swapchain_loop.md +++ b/guide/translations/ko-KR/src/rendering/swapchain_loop.md @@ -19,7 +19,10 @@ ## 가상 프레임 -프레임마다 사용되는 모든 동적 자원들은 가상 프레임에 포함됩니다. 애플리케이션은 고정된 개수의 가상 프레임을 가지고 있으며, 매 렌더 패스마다 이를 순환하며 사용합니다. 동기화를 위해 각 프레임은 이전 프레임의 렌더링이 끝날 때 까지 대기하게 만드는 [`vk::Fence`](https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-fences)가 있어야 합니다. 또한 GPU에서의 이미지를 받아오고, 렌더링, 화면에 나타내는 작업을 동기화하기 위한 2개의[`vk::Semaphore`](https://docs.vulkan.org/spec/latest/chapters/synchronization.html#synchronization-semaphores)가 필요합니다(이 작업들은 CPU측에서 대기할 필요는 없습니다). 명령을 기록하기 위해 가상 프레임마다 [`vk::CommandBuffer`](https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html)를 두어 해당 프레임의 (레이아웃 전환을 포함한) 모든 렌더링 명령을 기록할 것입니다. +프레임마다 사용되는 모든 동적 자원들은 가상 프레임에 포함됩니다. 애플리케이션은 고정된 개수의 가상 프레임을 가지고 있으며, 매 렌더 패스마다 이를 순환하며 사용합니다. 동기화를 위해 각 프레임은 이전 프레임의 렌더링이 끝날 때 까지 대기하게 만드는 [`vk::Fence`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkFence.html)가 있어야 합니다. 또한 GPU에서의 이미지를 받아오는 것과 렌더링하는 작업을 동기화하기 위한 [`vk::Semaphore`](https://registry.khronos.org/vulkan/specs/latest/man/html/VkSemaphore.html)가 필요합니다(이 작업들은 코드에서 대기할 필요는 없습니다). 명령을 기록하기 위해 가상 프레임마다 [`vk::CommandBuffer`](https://docs.vulkan.org/spec/latest/chapters/cmdbuffers.html)를 두어 해당 프레임의 (레이아웃 전환을 포함한) 모든 렌더링 명령을 기록할 것입니다. + + +화면 표시 작업에도 동기화를 위한 세마포어가 필요하지만, 스왑체인 루프는 각 가상 프레임이 처음 사용될 때 미리 시그널되는 drawn 펜스를 기준으로 대기하기 때문에, 표시용 세마포어는 가상 프레임의 일부가 될 수 없습니다. 아직 시그널되지 않은 표시용 세마포어를 사용하여 이미지를 가져오고 커맨드를 제출하는 것도 가능하지만, 이는 유효하지 않은 동작입니다. 따라서 이러한 세마포어는 스왑체인 이미지(인덱스)와 연결되며, 스왑체인이 재생성될 때 함께 재생성됩니다. ## 이미지 레이아웃 diff --git a/guide/translations/ko-KR/src/rendering/swapchain_update.md b/guide/translations/ko-KR/src/rendering/swapchain_update.md index 8455293..9c6d2ec 100644 --- a/guide/translations/ko-KR/src/rendering/swapchain_update.md +++ b/guide/translations/ko-KR/src/rendering/swapchain_update.md @@ -1,6 +1,42 @@ # 스왑체인 업데이트 -스왑체인에서 이미지를 받아오고 표시하는 작업은 다양한 결과를 반환할 수 있습니다. 우리는 다음과 같은 경우에 한정하여 처리합니다. +세마포어 vector를 추가하고 `recreate()`함수를 통해 이를 할당합니다. + +```cpp +void create_present_semaphores(); + +// ... +// signaled when image is ready to be presented. +std::vector 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({}); + } +} +``` + +가져온 이미지에 대응되는 표시용 세마포어를 가져오는 함수를 추가합니다. 이는 렌더링 커맨드 버퍼가 제출될 때 시그널 됩니다. + +```cpp +auto Swapchain::get_present_semaphore() const -> vk::Semaphore { + return *m_present_semaphores.at(m_image_index.value()); +} +``` + +스왑체인에서 이미지를 받아오고 표시하는 작업은 다양한 결과를 반환할 수 있습니다. 우리는 다음과 같은 경우로 한정하여 처리하겠습니다. - `eSuccess` : 문제가 없습니다. - `eSuboptimalKHR` : 역시 문제가 없습니다(에러는 아니며, 데스크탑 환경에서는 드물게 발생합니다). diff --git a/guide/translations/zh-TW/src/README.md b/guide/translations/zh-TW/src/README.md index edeb023..7b7f330 100644 --- a/guide/translations/zh-TW/src/README.md +++ b/guide/translations/zh-TW/src/README.md @@ -1,33 +1,40 @@ -# (中文 WIP) Intro +# Intro -Vulkan is known for being explicit and verbose. But the _required_ verbosity has steadily reduced with each successive version, its new features, and previous extensions being absorbed into the core API. Similarly, RAII has been a pillar of C++ since its inception, yet most Vulkan guides do not utilize it, instead choosing to "extend" the explicitness by manually cleaning up resources. +Vulkan 一向以顯式(explicit)與冗長著稱。 不過,這種「必要的冗長」隨著每一個版本的演進,以及新功能與舊有的 extension 被納入核心 API 後,正逐漸地在減少。 同樣地,RAII 自 C++ 發展以來就是其核心概念之一,但多數 Vulkan 教學卻沒有活用它,而是選擇用手動釋放資源的方式來延續這種「顯式性」 -To fill that gap, this guide has the following goals: +為了填補這個缺口,本教學有以下幾個目標: -- Leverage modern C++, VulkanHPP, and Vulkan 1.3 features -- Focus on keeping it simple and straightforward, _not_ on performance -- Develop a basic but dynamic rendering foundation +- 善用現代 C++、VulkanHPP,以及 Vulkan 1.3 的特性 +- 著重在簡潔與易懂,而不是效能 +- 建立一個基礎但具有動態特性的 rendering 架構 -To reiterate, the focus is _not on performance_, it is on a quick introduction to the current standard multi-platform graphics API while utilizing the modern paradigms and tools (at the time of writing). Even disregarding potential performance gains, Vulkan has a better and modern design and ecosystem than OpenGL, eg: there is no global state machine, parameters are passed by filling structs with meaningful member variable names, multi-threading is largely trivial (yes, it is actually easier to do on Vulkan than OpenGL), there are a comprehensive set of validation layers to catch misuse which can be enabled without _any_ changes to application code, etc. +要注意的是,這份教學的重點不是效能,而是快速介紹目前標準的跨平台繪圖 API,並配合當代主流的程式設計模式與工具(以本文撰寫當下為準)。 就算不考慮效能上的潛在優勢,Vulkan 的設計本身也比 OpenGL 更現代化、更完善,例如:沒有全域狀態機、所有參數都透過 struct 傳遞且欄位名稱具語意、多執行緒的支援非常直接(沒錯,在 Vulkan 上其實比 OpenGL 更好做),還有提供一整套的 validation layer,讓你開發的時候可以在不需改動任何程式碼的情況下捕捉到一些誤用(misuse) ## Target Audience -The guide is for you if you: +如果你符合以下條件,那這份教學會很適合你: -- Understand the principles of modern C++ and its usage -- Have created C++ projects using third-party libraries -- Are somewhat familiar with graphics - - Having done OpenGL tutorials would be ideal - - Experience with frameworks like SFML / SDL is great -- Don't mind if all the information you need isn't monolithically in one place (ie, this guide) +- 了解現代 C++ 的原則與用法 +- 有建立過使用第三方函式庫的 C++ 專案 +- 對繪圖領域有一些基本認識 + - 如果已做過 OpenGL 的教學會很好 + - 有使用過像 SFML / SDL 這類 framework 的經驗也很好 +- 不介意教學不會涵蓋所有細節的話 -Some examples of what this guide _does not_ focus on: +這份教學「不會」對以下主題做太多著墨: -- GPU-driven rendering -- Real-time graphics from ground-up -- Considerations for tiled GPUs (eg mobile devices / Android) +- 由 GPU 主導的 rendering 架構 +- 從零開始打造即時繪圖引擎 +- 專為 tile-based GPU 考量的實作(例如行動裝置 / Android) ## Source -The source code for the project (as well as this guide) is located in [this repository](https://github.com/cpp-gamedev/learn-vulkan). A `section/*` branch intends to reflect the state of the code at the end of a particular section of the guide. Bugfixes / changes are generally backported, but there may be some divergence from the current state of the code (ie, in `main`). The source of the guide itself is only up-to-date on `main`, changes are not backported. +本專案的原始碼(以及這份教學的內容)都放在[這個 repository](https://github.com/cpp-gamedev/learn-vulkan) 裡。 repo 中的每個 `section/*` 分支(branch)會對應到教學中特定章節結束時的程式狀態。 一般來說修正的 bug 與後續的更新都會回補進這些分支,但仍可能會與主分支(`main`)上的最新程式碼有些出入。 教學本身的文章內容只有在 `main` 分支上是最新的,這些變更並不會被回補進其他分支 +## 譯者的話(by Mes) + +感謝 karnage 寫的文章,他是一位非常厲害的工程師,非常熟悉現代 C++ 與遊戲引擎相關的技術,問他問題時他也都回的非常有耐心,人很 nice + +翻譯時我會盡量還原原文的意思,但有時候會斟酌對譯文做一些增減以更符合其原語意。 如果發現翻譯部分有錯,歡迎直接發 issue 或 PR 過來。 如果閱讀上有什麼問題,也可以直接發 issue 或是到我們的 Discord 中詢問 + +譯文的排版上基本以[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)為主,但由於本人的習慣,在段落的句尾並不會加上句號,另外在全形標點符號的前後我會視情況加上空格以方便閱讀。 每個段落的行數會盡量控制在 2~4 行,因此視情況會對原文中較長的段落進行分段。 對於一些較難翻的詞意我會於其第一次出現的地方以括號的形式補上原文,而對於標題、TOC 與程式碼中的註解,則會統一直接保留原文 diff --git a/guide/translations/zh-TW/src/SUMMARY.md b/guide/translations/zh-TW/src/SUMMARY.md index 7fc1a4b..9bb6ddf 100644 --- a/guide/translations/zh-TW/src/SUMMARY.md +++ b/guide/translations/zh-TW/src/SUMMARY.md @@ -1,10 +1,10 @@ -# (中文 WIP) Summary +# Summary -[(中文 WIP) Introduction](README.md) +[Introduction](README.md) -# (中文 WIP) Basics +# Basics -- [(中文 WIP) Getting Started](getting_started/README.md) - - [(中文 WIP) Project Layout](getting_started/project_layout.md) - - [(中文 WIP) Validation Layers](getting_started/validation_layers.md) - - [(中文 WIP) class App](getting_started/class_app.md) +- [Getting Started](getting_started/README.md) + - [Project Layout](getting_started/project_layout.md) + - [Validation Layers](getting_started/validation_layers.md) + - [class App](getting_started/class_app.md) diff --git a/guide/translations/zh-TW/src/getting_started/README.md b/guide/translations/zh-TW/src/getting_started/README.md index 53b33c2..3d5daa9 100644 --- a/guide/translations/zh-TW/src/getting_started/README.md +++ b/guide/translations/zh-TW/src/getting_started/README.md @@ -1,34 +1,34 @@ -# (中文 WIP) Getting Started +# Getting Started -Vulkan is platform agnostic, which is one of the main reasons for its verbosity: it has to account for a wide range of implementations in its API. We shall be constraining our approach to Windows and Linux (x64 or aarch64), and focusing on discrete GPUs, enabing us to sidestep quite a bit of that verbosity. Vulkan 1.3 is widely supported by the target desktop platforms and reasonably recent graphics cards. +Vulkan 是跨平台的 API,這也是它之所以冗長的一個主要原因:它的 API 必須涵蓋各式各樣的實作方式。 我們這裡會將範圍限縮在 Windows 和 Linux(x64 或 aarch64),並專注於獨立顯示卡,這樣可以避開不少冗長的細節,而且這些桌面平台與近幾代的顯示卡上都已廣泛支援 Vulkan 1.3 了 -> This doesn't mean that eg an integrated graphics chip will not be supported, it will just not be particularly designed/optimized for. +> 這並不代表像是整合型顯示晶片(integrated graphics)就不被支援,只是它們的設計或最佳化方向通常不是針對 Vulkan 而來 ## Technical Requirements -1. Vulkan 1.3+ capable GPU and loader -1. [Vulkan 1.3+ SDK](https://vulkan.lunarg.com/sdk/home) - 1. This is required for validation layers, a critical component/tool to use when developing Vulkan applications. The project itself does not use the SDK. - 1. Always using the latest SDK is recommended (1.4.x at the time of writing). -1. Desktop operating system that natively supports Vulkan - 1. Windows and/or Linux (distros that use repos with recent packages) is recommended. - 1. MacOS does _not_ natively support Vulkan. It _can_ be used through MoltenVk, but at the time of writing MoltenVk does not fully support Vulkan 1.3, so if you decide to take this route, you may face some roadblocks. -1. C++23 compiler and standard library - 1. GCC14+, Clang18+, and/or latest MSVC are recommended. MinGW/MSYS is _not_ recommended. - 1. Using C++20 with replacements for C++23 specific features is possible. Eg replace `std::print()` with `fmt::print()`, add `()` to lambdas, etc. -1. CMake 3.24+ +1. 支援 Vulkan 1.3 以上的 GPU 與 loader +2. [Vulkan 1.3+ SDK](https://vulkan.lunarg.com/sdk/home) + 1. 在開發 Vulkan 的應用程式時 validation layer 是必要的組件/工具,但本專案本身不會直接使用 SDK + 2. 建議始終使用最新版的 SDK(撰寫本文時為 1.4.x) +3. 原生支援(natively support)Vulkan 的桌面作業系統 + 1. 推薦使用 Windows 或有新套件庫的 Linux 發行版 + 2. MacOS 並不原生支援 Vulkan。 雖然可以透過 MoltenVk 使用,但撰寫本文時 MoltenVk 尚未完整支援 Vulkan 1.3,因此如果你選擇走這條路,可能會遇到一些阻礙 +4. 支援 C++23 的編譯器與標準函式庫 + 1. 推薦使用 GCC14+、Clang18+ 或最新版本的 MSVC。 不推薦使用 MinGW/MSYS + 2. 也可以使用 C++20 並搭配替代方案來補齊 C++23 的功能,例如將 `std::print()` 改成 `fmt::print()`,或是在 lambda 後面加上 `()` 等等 +5. CMake 3.24 以上的版本 ## Overview -While support for C++ modules is steadily growing, tooling is not yet ready on all platforms/IDEs we want to target, so we will unfortuntely still be using headers. This might change in the near future, followed by a refactor of this guide. +雖然 C++ modules 正慢慢的開始普及了,但於我們的目標平台與 IDE 上,其工具鏈仍不夠完善,因此目前仍會使用傳統的 header 檔。 未來這情況可能會改變,到時這份教學也會跟著重構 -The project uses a "Build the World" approach, enabling usage of sanitizers, reproducible builds on any supported platform, and requiring minimum pre-installed things on target machines. Feel free to use pre-built binaries instead, it doesn't change anything about how you would use Vulkan. +本專案採用「Build the World」的方式,這讓我們可以使用 sanitizer、在所有目標平台上都能重現編譯結果,並且不必於目標機器上預先安裝太多東西。 當然你也可以選擇使用預先編好的 binary,這不會影響你使用 Vulkan 的方式 ## Dependencies -1. [GLFW](https://github.com/glfw/glfw) for windowing, input, and Surface creation -1. [VulkanHPP](https://github.com/KhronosGroup/Vulkan-Hpp) (via [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers)) for interacting with Vulkan - 1. While Vulkan is a C API, it offers an official C++ wrapper library with many quality-of-life features. This guide almost exclusively uses that, except at the boundaries of other C libraries that themselves use the C API (eg GLFW and VMA). -1. [Vulkan Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/) for dealing with Vulkan memory heaps -1. [GLM](https://github.com/g-truc/glm) for GLSL-like linear algebra in C++ -1. [Dear ImGui](https://github.com/ocornut/imgui) for UI +1. [GLFW](https://github.com/glfw/glfw) 用來建立視窗、處理輸入與建立 Vulkan Surface +2. [VulkanHPP](https://github.com/KhronosGroup/Vulkan-Hpp)(透過 [Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers))用來與 Vulkan 互動 + 1. 雖然 Vulkan 是 C 的 API,但官方提供了 C++ 的 wrapper library,加入了許多提升開發體驗的功能。 本教學幾乎都使用 C++ 版本的包裝,只有在需要與其他使用 C API 的函式庫互動時(如 GLFW 和 VMA)才會使用原生的 C API +3. [Vulkan Memory Allocator](https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/) 用來處理 Vulkan 的記憶體配置與管理 +4. [GLM](https://github.com/g-truc/glm) 提供類似 GLSL 的線性代數功能給 C++ 使用 +5. [Dear ImGui](https://github.com/ocornut/imgui) 用來建立 UI 介面 diff --git a/guide/translations/zh-TW/src/getting_started/class_app.md b/guide/translations/zh-TW/src/getting_started/class_app.md index 112ea26..c4991c9 100644 --- a/guide/translations/zh-TW/src/getting_started/class_app.md +++ b/guide/translations/zh-TW/src/getting_started/class_app.md @@ -1,6 +1,6 @@ -# (中文 WIP) Application +# Application -`class App` will serve as the owner and driver of the entire application. While there will only be one instance, using a class enables us to leverage RAII and destroy all its resources automatically and in the correct order, and avoids the need for globals. +`class App` 會作為整個應用程式的擁有者與執行者。 雖然其實際上只會建立一個實例,但使用 class 可以讓我們善用 RAII,自動且按正確順序釋放所有資源,並避免使用全域變數 ```cpp // app.hpp @@ -21,7 +21,7 @@ void App::run() { ## Main -`main.cpp` will not do much: it's mainly responsible for transferring control to the actual entry point, and catching fatal exceptions. +`main.cpp` 不會負責太多事:它主要的工作是將控制權交給真正的進入點,並攔截致命的例外錯誤 ```cpp // main.cpp diff --git a/guide/translations/zh-TW/src/getting_started/project_layout.md b/guide/translations/zh-TW/src/getting_started/project_layout.md index a993c3e..22380e5 100644 --- a/guide/translations/zh-TW/src/getting_started/project_layout.md +++ b/guide/translations/zh-TW/src/getting_started/project_layout.md @@ -1,14 +1,14 @@ -# (中文 WIP) Project Layout +# Project Layout -This page describes the layout used by the code in this guide. Everything here is just an opinionated option used by the guide, and is not related to Vulkan usage. +本頁說明這份教學中所使用的程式碼結構。 這些安排只是本教學採用的一種主觀選擇,與 Vulkan 的使用方式本身無關 -External dependencies are stuffed into a zip file that's decompressed by CMake during the configure stage. Using FetchContent is a viable alternative. +外部的依賴庫會被打包成一個 zip 檔,並在 CMake 的 configure 階段中被解壓縮。 你也可以考慮將 FetchContent 作為替代方案 -`Ninja Multi-Config` is the assumed generator used, regardless of OS/compiler. This is set up in a `CMakePresets.json` file in the project root. Additional custom presets can be added via `CMakeUserPresets.json`. +預設使用的建構器(generator)是 `Ninja Multi-Config`,不論哪個作業系統或編譯器都是如此。 這會在專案根目錄下的 `CMakePresets.json` 中設定。 你也可以透過 `CMakeUserPresets.json` 加入自定的 preset 設定 -> On Windows, Visual Studio CMake Mode uses this generator and automatically loads presets. With Visual Studio Code, the CMake Tools extension automatically uses presets. For other IDEs, refer to their documentation on using CMake presets. +> 在 Windows 上,Visual Studio 的 CMake 模式會使用這個建構器並自動載入 preset。 若使用 Visual Studio Code,CMake Tools 擴充套件也會自動使用這些 preset。 至於其他 IDE 則請參考它們對於 CMake preset 的使用說明 -**Filesystem** +**檔案系統結構** ``` . @@ -19,4 +19,4 @@ External dependencies are stuffed into a zip file that's decompressed by CMake d │ |-- CMakeLists.txt <== external dependencies target |-- src/ |-- [sources and headers] -``` \ No newline at end of file +``` diff --git a/guide/translations/zh-TW/src/getting_started/validation_layers.md b/guide/translations/zh-TW/src/getting_started/validation_layers.md index 42494a8..f9f5e84 100644 --- a/guide/translations/zh-TW/src/getting_started/validation_layers.md +++ b/guide/translations/zh-TW/src/getting_started/validation_layers.md @@ -1,9 +1,13 @@ -# (中文 WIP) Validation Layers +# Validation Layers -The area of Vulkan that apps interact with: the loader, is very powerful and flexible. Read more about it [here](https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderInterfaceArchitecture.md). Its design enables it to chain API calls through configurable **layers**, eg for overlays, and most importantly for us: [Validation Layers](https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/README.md). +Vulkan 中與應用程式互動的部分為 Vulkan loader,其本身非常強大且具有彈性。 你可以在[這裡](https://github.com/KhronosGroup/Vulkan-Loader/blob/main/docs/LoaderInterfaceArchitecture.md)讀取更詳細的說明。 它的設計使我們能夠透過可配置的「layer」來串接 API 的呼叫,像是 overlay,或對我們來說最重要的「[Validation Layers](https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/README.md)」 ![Vulkan Loader](high_level_loader.png) -As [suggested](https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/khronos_validation_layer.md#vkconfig) by the Khronos Group, we recommend using [Vulkan Configurator (GUI)](https://github.com/LunarG/VulkanTools/tree/main/vkconfig_gui) for validation layers. This application is shipped with the Vulkan SDK, just keep it running while developing Vulkan applications, and ensure it is setup to inject validation layers into all detected applications, with Synchronization Validation enabled. This approach provides a lot of flexibility at runtime, including the ability to have VkConfig break the debugger on encountering an error, and also eliminates the need for validation layer specific code in the applications. +根據 Khronos Group 的[建議](https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/khronos_validation_layer.md#vkconfig),我們推薦使用 [Vulkan Configurator (GUI)](https://github.com/LunarG/VulkanTools/tree/main/vkconfig_gui) 來啟用 validation layer。 這個應用程式會隨 Vulkan SDK 一起提供,只要在開發 Vulkan 應用時讓它保持執行,並確保它被設定為會將 validation layer 注入所有被偵測到的應用程式中,並啟用 Synchronization Validation 即可 -> Note: modify your development (or desktop) environment's `PATH` (or use `LD_LIBRARY_PATH` on supported systems) to make sure the SDK's binaries are visible first. \ No newline at end of file +這樣的做法能提供極高的執行期彈性,例如可以讓 VkConfig 在遇到錯誤時觸發中斷進入除錯器,而不需要在應用程式中撰寫任何與 validation layer 有關的程式碼 + +> 注意:記得修改你開發環境(或桌面環境)的 `PATH`(在支援的系統上也可以用 `LD_LIBRARY_PATH`),確保 SDK 的執行檔能優先被系統找到 + +![Vulkan Configurator](./vkconfig_gui.png) diff --git a/guide/translations/zh-TW/src/getting_started/vkconfig_gui.png b/guide/translations/zh-TW/src/getting_started/vkconfig_gui.png new file mode 100644 index 0000000..4ebe0fa Binary files /dev/null and b/guide/translations/zh-TW/src/getting_started/vkconfig_gui.png differ diff --git a/src/app.cpp b/src/app.cpp index e68e275..cd407bb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -229,7 +229,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); } } @@ -535,7 +534,7 @@ void App::submit_and_present() { wait_semaphore_info.setSemaphore(*render_sync.draw) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); auto signal_semaphore_info = vk::SemaphoreSubmitInfo{}; - signal_semaphore_info.setSemaphore(*render_sync.present) + signal_semaphore_info.setSemaphore(m_swapchain->get_present_semaphore()) .setStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput); submit_info.setCommandBufferInfos(command_buffer_info) .setWaitSemaphoreInfos(wait_semaphore_info) @@ -549,8 +548,7 @@ void App::submit_and_present() { // framebuffer size does not match the Swapchain image size, check it // explicitly. auto const fb_size_changed = m_framebuffer_size != m_swapchain->get_size(); - auto const out_of_date = - !m_swapchain->present(m_queue, *render_sync.present); + auto const out_of_date = !m_swapchain->present(m_queue); if (fb_size_changed || out_of_date) { m_swapchain->recreate(m_framebuffer_size); } diff --git a/src/app.hpp b/src/app.hpp index aa07613..cd2aba8 100644 --- a/src/app.hpp +++ b/src/app.hpp @@ -22,11 +22,9 @@ class App { private: struct RenderSync { - // signalled when Swapchain image has been acquired. + // signaled when Swapchain image has been acquired. vk::UniqueSemaphore draw{}; - // signalled when image is ready to be presented. - vk::UniqueSemaphore present{}; - // signalled with present Semaphore, waited on before next render. + // signaled with present Semaphore, waited on before next render. vk::UniqueFence drawn{}; // used to record rendering commands. vk::CommandBuffer command_buffer{}; diff --git a/src/swapchain.cpp b/src/swapchain.cpp index fd08af0..aa32c82 100644 --- a/src/swapchain.cpp +++ b/src/swapchain.cpp @@ -120,6 +120,8 @@ 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(); size = get_size(); std::println("[lvk] Swapchain [{}x{}]", size.x, size.y); @@ -155,13 +157,18 @@ auto Swapchain::base_barrier() const -> vk::ImageMemoryBarrier2 { return ret; } -auto Swapchain::present(vk::Queue const queue, vk::Semaphore const to_wait) - -> bool { +auto Swapchain::get_present_semaphore() const -> vk::Semaphore { + return *m_present_semaphores.at(m_image_index.value()); +} + +auto Swapchain::present(vk::Queue const queue) -> bool { auto const image_index = static_cast(m_image_index.value()); + auto const wait_semaphore = + *m_present_semaphores.at(static_cast(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(); @@ -195,4 +202,12 @@ void Swapchain::create_image_views() { m_image_views.push_back(m_device.createImageViewUnique(image_view_ci)); } } + +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({}); + } +} } // namespace lvk diff --git a/src/swapchain.hpp b/src/swapchain.hpp index 3ad5d13..5892192 100644 --- a/src/swapchain.hpp +++ b/src/swapchain.hpp @@ -26,11 +26,13 @@ class Swapchain { [[nodiscard]] auto base_barrier() const -> vk::ImageMemoryBarrier2; - [[nodiscard]] auto present(vk::Queue queue, vk::Semaphore to_wait) -> bool; + [[nodiscard]] auto get_present_semaphore() const -> vk::Semaphore; + [[nodiscard]] auto present(vk::Queue queue) -> bool; private: void populate_images(); void create_image_views(); + void create_present_semaphores(); vk::Device m_device{}; Gpu m_gpu{}; @@ -39,6 +41,8 @@ class Swapchain { vk::UniqueSwapchainKHR m_swapchain{}; std::vector m_images{}; std::vector m_image_views{}; + // signaled when image is ready to be presented. + std::vector m_present_semaphores{}; std::optional m_image_index{}; }; } // namespace lvk