Source code for simvx.graphics.gpu.sync

"""Fences, semaphores, and frames-in-flight synchronization."""


from __future__ import annotations

import logging
from typing import Any

import vulkan as vk

from .._types import FRAMES_IN_FLIGHT

__all__ = ["FrameSync"]

log = logging.getLogger(__name__)


[docs] class FrameSync: """Per-frame synchronization primitives. Uses FRAMES_IN_FLIGHT fences and acquire semaphores, plus one render-finished semaphore per swapchain image to avoid the semaphore-reuse hazard described in the Vulkan swapchain guide. """ def __init__(self, device: Any, image_count: int, frame_count: int = FRAMES_IN_FLIGHT) -> None: self.device = device self.frame_count = frame_count self.image_count = image_count self.current_frame = 0 # Per frame-in-flight self.fences: list[Any] = [] self.image_available: list[Any] = [] # Per swapchain image self.render_finished: list[Any] = [] # Track which fence guards each swapchain image self.images_in_flight: list[Any] = []
[docs] def create(self) -> None: fence_info = vk.VkFenceCreateInfo(flags=vk.VK_FENCE_CREATE_SIGNALED_BIT) sem_info = vk.VkSemaphoreCreateInfo() for _ in range(self.frame_count): self.fences.append(vk.vkCreateFence(self.device, fence_info, None)) self.image_available.append(vk.vkCreateSemaphore(self.device, sem_info, None)) for _ in range(self.image_count): self.render_finished.append(vk.vkCreateSemaphore(self.device, sem_info, None)) self.images_in_flight = [None] * self.image_count log.debug("Frame sync created (%d frames, %d images)", self.frame_count, self.image_count)
[docs] def wait_and_reset(self) -> None: fence = self.fences[self.current_frame] vk.vkWaitForFences(self.device, 1, [fence], vk.VK_TRUE, 2_000_000_000) vk.vkResetFences(self.device, 1, [fence])
[docs] def wait_for_image(self, image_index: int) -> None: """Wait for any in-flight work using this swapchain image.""" fence = self.images_in_flight[image_index] if fence is not None and fence != self.fences[self.current_frame]: vk.vkWaitForFences(self.device, 1, [fence], vk.VK_TRUE, 2_000_000_000)
[docs] def mark_image(self, image_index: int) -> None: """Associate this swapchain image with the current frame's fence.""" self.images_in_flight[image_index] = self.fences[self.current_frame]
[docs] def advance(self) -> None: self.current_frame = (self.current_frame + 1) % self.frame_count
[docs] def destroy(self) -> None: for sem in self.render_finished: vk.vkDestroySemaphore(self.device, sem, None) for i in range(self.frame_count): vk.vkDestroySemaphore(self.device, self.image_available[i], None) vk.vkDestroyFence(self.device, self.fences[i], None) self.fences.clear() self.image_available.clear() self.render_finished.clear() self.images_in_flight.clear()