Program Listing for File FrameSync.ixx#
↰ Return to documentation for file (Src/GraphicsEngineVulkan/renderer/FrameSync.ixx)
module;
#include <algorithm>
#include <cstdint>
#include <vector>
#include <vulkan/vulkan.hpp>
#include "common/Globals.hpp"
#include "spdlog/spdlog.h"
export module kataglyphis.vulkan.frame_sync;
export namespace Kataglyphis {
// Owns the renderer's frame-synchronization primitives, extracted verbatim
// from VulkanRenderer. The deliberate split in sizing is the whole point of
// this class and must be preserved:
// - image_available / in_flight_fences are sized PER FRAME-IN-FLIGHT
// (frame_sync_count = min(MAX_FRAME_DRAWS, swapchain image count)); the
// CPU cycles current_frame through this set.
// - render_finished_by_image / images_in_flight_fences are sized PER
// SWAPCHAIN IMAGE. A per-frame render-finished semaphore can be waited on
// before it is signalled across a swapchain recreate, so render-finished
// is keyed by image, not by frame.
class FrameSync
{
public:
// Allocates image_available/in_flight_fences sized frame_sync_count and
// render_finished_by_image/images_in_flight_fences sized imageCount,
// recreating from scratch (destroys any existing set first). On any
// creation failure frame_sync_count is set to 0 and the function returns
// early, exactly as the renderer did inline.
void create(vk::Device logicalDevice, uint32_t imageCount)
{
resetAndSize(logicalDevice, imageCount);
vk::SemaphoreCreateInfo semaphore_create_info{};
vk::FenceCreateInfo fence_create_info{};
fence_create_info.flags = vk::FenceCreateFlagBits::eSignaled;
for (uint32_t i = 0; i < frame_sync_count; i++) {
auto image_available_result_value = logicalDevice.createSemaphore(semaphore_create_info);
auto image_available_result = image_available_result_value.result;
auto image_available_handle = image_available_result_value.value;
auto in_flight_fence_result_value = logicalDevice.createFence(fence_create_info);
auto in_flight_fence_result = in_flight_fence_result_value.result;
auto in_flight_fence_handle = in_flight_fence_result_value.value;
if (image_available_result != vk::Result::eSuccess || in_flight_fence_result != vk::Result::eSuccess
|| !image_available_handle || !in_flight_fence_handle) {
spdlog::error(
fmt::format("Failed to create synchronization objects for frame {} (imageAvailable={}, fence={}).",
i,
static_cast<int>(image_available_result),
static_cast<int>(in_flight_fence_result)));
cleanUp(logicalDevice);
return;
}
image_available[i] = image_available_handle;
in_flight_fences[i] = in_flight_fence_handle;
}
for (uint32_t image = 0; image < imageCount; ++image) {
auto render_finished_result_value = logicalDevice.createSemaphore(semaphore_create_info);
auto render_finished_result = render_finished_result_value.result;
auto render_finished_handle = render_finished_result_value.value;
if (render_finished_result != vk::Result::eSuccess || !render_finished_handle) {
spdlog::error(fmt::format("Failed to create render-finished semaphore for swapchain image {} ({}).",
image,
static_cast<int>(render_finished_result)));
cleanUp(logicalDevice);
return;
}
render_finished_by_image[image] = render_finished_handle;
}
for (uint32_t image = 0; image < imageCount; ++image) {
images_in_flight_fences[image] = nullptr;
}
current_frame = 0;
}
// Device-free half of create(): tears down any existing set, computes the
// frame_sync_count/imageCount split and resizes the four handle vectors
// to match. cleanUp() zeroes frame_sync_count, so the sizing must be
// computed after it, not before.
void resetAndSize(vk::Device logicalDevice, uint32_t imageCount)
{
cleanUp(logicalDevice);
frame_sync_count =
std::min<uint32_t>(static_cast<uint32_t>(Kataglyphis::MAX_FRAME_DRAWS), imageCount);
image_available.resize(frame_sync_count);
render_finished_by_image.resize(imageCount);
in_flight_fences.resize(frame_sync_count);
images_in_flight_fences.resize(imageCount);
}
void cleanUp(vk::Device logicalDevice)
{
// Iterate each vector on its own: after a partial create failure their
// lengths can differ, and a shared loop bound would leak whatever the
// longer vector still holds.
for (vk::Semaphore semaphore : render_finished_by_image) {
if (semaphore) { logicalDevice.destroySemaphore(semaphore); }
}
render_finished_by_image.clear();
for (vk::Semaphore semaphore : image_available) {
if (semaphore) { logicalDevice.destroySemaphore(semaphore); }
}
image_available.clear();
for (vk::Fence fence : in_flight_fences) {
if (fence) { logicalDevice.destroyFence(fence); }
}
in_flight_fences.clear();
images_in_flight_fences.clear();
frame_sync_count = 0;
current_frame = 0;
}
// current_frame = (current_frame + 1) % frame_sync_count; frame_sync_count
// is min(MAX_FRAME_DRAWS, image count), not MAX_FRAME_DRAWS directly, so a
// swapchain with fewer images than MAX_FRAME_DRAWS still cycles correctly.
void advanceFrame()
{
// create() can fail (and zero frame_sync_count) after the caller's
// frameSyncCount()==0 guard has already run this frame; guard the
// modulo here too so that ordering can never divide by zero.
if (frame_sync_count == 0) {
current_frame = 0;
return;
}
current_frame = (current_frame + 1) % frame_sync_count;
}
[[nodiscard]] uint32_t currentFrame() const { return current_frame; }
[[nodiscard]] uint32_t frameSyncCount() const { return frame_sync_count; }
[[nodiscard]] bool inFlightFencesEmpty() const { return in_flight_fences.empty(); }
[[nodiscard]] size_t imageAvailableCount() const { return image_available.size(); }
[[nodiscard]] size_t inFlightFenceCount() const { return in_flight_fences.size(); }
[[nodiscard]] size_t renderFinishedCount() const { return render_finished_by_image.size(); }
[[nodiscard]] size_t imagesInFlightFenceCount() const { return images_in_flight_fences.size(); }
// Handles for the current frame-in-flight (indexed by current_frame).
// Returned by reference so the draw path can take their address for the
// waitForFences/resetFences/submit calls that expect a pointer to the
// stored handle.
vk::Semaphore &imageAvailableSemaphore() { return image_available[current_frame]; }
vk::Fence &inFlightFence() { return in_flight_fences[current_frame]; }
// Handles keyed by swapchain image index.
vk::Semaphore &renderFinishedSemaphore(uint32_t image_index) { return render_finished_by_image[image_index]; }
vk::Fence &imageInFlightFence(uint32_t image_index) { return images_in_flight_fences[image_index]; }
private:
uint32_t current_frame{ 0 };
uint32_t frame_sync_count{ 1 };
std::vector<vk::Semaphore> image_available;
std::vector<vk::Semaphore> render_finished_by_image;
std::vector<vk::Fence> in_flight_fences;
std::vector<vk::Fence> images_in_flight_fences;
};
}// namespace Kataglyphis