telar_renderer_core/gpu_sync.rs
1//! Process-global lock serializing GPU/window surface *lifecycle* against active rendering.
2//!
3//! With M3 several hardware surfaces live in one process, each rendering on its own thread. On Wayland the
4//! `wl_surface` and the Vulkan swapchain are driven from different threads — the main thread owns the winit
5//! window, the render thread acquires/presents. Creating or destroying one window's surface on the main
6//! thread while another window's render thread is inside `vkAcquireNextImageKHR` corrupts the shared driver
7//! connection and segfaults (reproduced on the NVIDIA driver with two hardware surfaces). Render threads run
8//! concurrently under a shared *read* guard; a surface's creation or teardown takes the exclusive *write*
9//! guard, which waits for every in-flight frame to finish and blocks new ones — so lifecycle can never
10//! overlap a live acquire/present.
11
12use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
13
14static GPU_LIFECYCLE: RwLock<()> = RwLock::new(());
15
16/// Held for the duration of a render thread's frame (acquire → present); concurrent with other render
17/// threads, but mutually exclusive with surface lifecycle. The `()` payload has no invariants, so a panic
18/// poisoning the lock is irrelevant — recover and continue.
19pub fn render_guard() -> RwLockReadGuard<'static, ()> {
20 GPU_LIFECYCLE.read().unwrap_or_else(|e| e.into_inner())
21}
22
23/// Held while a window/renderer surface is created or destroyed; exclusive against every render thread. The
24/// caller must not already hold a render guard on this thread (would self-deadlock).
25pub fn lifecycle_guard() -> RwLockWriteGuard<'static, ()> {
26 GPU_LIFECYCLE.write().unwrap_or_else(|e| e.into_inner())
27}