shdrlib 0.1.5

A three-tiered Vulkan shader compilation and rendering framework built in pure Rust
Documentation
//! Sync module - Fences and semaphores
//!
//! **WARNING**: CORE objects do NOT enforce lifetime dependencies.
//! Sync objects must not outlive the device they were created from.

use crate::core::Device;
use ash::vk;
use thiserror::Error;

/// Fence operation error
#[derive(Debug, Error)]
pub enum FenceError {
    #[error("Fence creation failed: {0}")]
    CreationFailed(vk::Result),

    #[error("Fence wait failed: {0}")]
    WaitFailed(vk::Result),

    #[error("Fence reset failed: {0}")]
    ResetFailed(vk::Result),
}

/// Semaphore operation error
#[derive(Debug, Error)]
pub enum SemaphoreError {
    #[error("Semaphore creation failed: {0}")]
    CreationFailed(vk::Result),
}

/// Fence wrapper for CPU-GPU synchronization
///
/// Fences are used to synchronize the CPU with GPU operations.
///
/// # Safety
///
/// This object does NOT enforce that the device outlives it.
/// Using the fence after dropping the device causes undefined behavior.
pub struct Fence {
    fence: vk::Fence,
}

impl Fence {
    /// Create a new fence
    ///
    /// # Arguments
    ///
    /// * `device` - The device to create the fence on
    /// * `signaled` - If true, fence starts in signaled state
    ///
    /// # Errors
    ///
    /// Returns `FenceError::CreationFailed` if creation fails.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use shdrlib::core::{Fence, Device};
    ///
    /// # fn example(device: &Device) -> Result<(), shdrlib::core::FenceError> {
    /// let fence = Fence::new(device, true)?; // Start signaled
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(device: &Device, signaled: bool) -> Result<Self, FenceError> {
        let flags = if signaled {
            vk::FenceCreateFlags::SIGNALED
        } else {
            vk::FenceCreateFlags::empty()
        };

        let create_info = vk::FenceCreateInfo {
            flags,
            ..Default::default()
        };

        // SAFETY: device is valid, create_info is properly initialized
        let fence = unsafe {
            device
                .handle()
                .create_fence(&create_info, None)
                .map_err(FenceError::CreationFailed)?
        };

        Ok(Self { fence })
    }

    /// Get the raw Vulkan fence handle
    #[inline]
    pub fn handle(&self) -> vk::Fence {
        self.fence
    }

    /// Wait for the fence to be signaled
    ///
    /// # Arguments
    ///
    /// * `device` - The device handle
    /// * `timeout` - Timeout in nanoseconds (use `u64::MAX` for infinite)
    ///
    /// # Errors
    ///
    /// Returns `FenceError::WaitFailed` if wait fails or times out.
    pub fn wait(&self, device: &Device, timeout: u64) -> Result<(), FenceError> {
        // SAFETY: device and fence are valid
        unsafe {
            device
                .handle()
                .wait_for_fences(&[self.fence], true, timeout)
                .map_err(FenceError::WaitFailed)
        }
    }

    /// Reset the fence to unsignaled state
    ///
    /// # Errors
    ///
    /// Returns `FenceError::ResetFailed` if reset fails.
    pub fn reset(&self, device: &Device) -> Result<(), FenceError> {
        // SAFETY: device and fence are valid
        unsafe {
            device
                .handle()
                .reset_fences(&[self.fence])
                .map_err(FenceError::ResetFailed)
        }
    }

    /// Destroy the fence manually
    ///
    /// # Safety
    ///
    /// The device must be the same device used to create this fence.
    /// After calling destroy(), the fence handle is invalidated.
    pub fn destroy(&self, device: &Device) {
        // SAFETY: device and fence are valid
        unsafe {
            device.handle().destroy_fence(self.fence, None);
        }
    }
}

impl Drop for Fence {
    fn drop(&mut self) {
        // Note: Cannot call destroy here because we don't have device reference
        // User must ensure device outlives fence, or call destroy() manually
        // This is a known limitation of CORE tier
        if self.fence != vk::Fence::null() {
            eprintln!("WARNING: Fence dropped without calling .destroy() - potential memory leak");
        }
    }
}

/// Semaphore wrapper for GPU-GPU synchronization
///
/// Semaphores are used to synchronize operations between queues.
///
/// # Safety
///
/// This object does NOT enforce that the device outlives it.
/// Using the semaphore after dropping the device causes undefined behavior.
pub struct Semaphore {
    semaphore: vk::Semaphore,
}

impl Semaphore {
    /// Create a new semaphore
    ///
    /// # Errors
    ///
    /// Returns `SemaphoreError::CreationFailed` if creation fails.
    ///
    /// # Example
    ///
    /// ```rust,no_run
    /// use shdrlib::core::{Semaphore, Device};
    ///
    /// # fn example(device: &Device) -> Result<(), shdrlib::core::SemaphoreError> {
    /// let semaphore = Semaphore::new(device)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn new(device: &Device) -> Result<Self, SemaphoreError> {
        let create_info = vk::SemaphoreCreateInfo::default();

        // SAFETY: device is valid, create_info is properly initialized
        let semaphore = unsafe {
            device
                .handle()
                .create_semaphore(&create_info, None)
                .map_err(SemaphoreError::CreationFailed)?
        };

        Ok(Self { semaphore })
    }

    /// Get the raw Vulkan semaphore handle
    #[inline]
    pub fn handle(&self) -> vk::Semaphore {
        self.semaphore
    }

    /// Destroy the semaphore manually
    ///
    /// # Safety
    ///
    /// The device must be the same device used to create this semaphore.
    /// After calling destroy(), the semaphore handle is invalidated.
    pub fn destroy(&self, device: &Device) {
        // SAFETY: device and semaphore are valid
        unsafe {
            device.handle().destroy_semaphore(self.semaphore, None);
        }
    }
}

impl Drop for Semaphore {
    fn drop(&mut self) {
        // Note: Cannot call destroy here because we don't have device reference
        // User must ensure device outlives semaphore, or call destroy() manually
        // This is a known limitation of CORE tier
        if self.semaphore != vk::Semaphore::null() {
            eprintln!(
                "WARNING: Semaphore dropped without calling .destroy() - potential memory leak"
            );
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::{Device, DeviceCreateInfo, Instance, InstanceCreateInfo, QueueCreateInfo};

    fn create_test_device() -> (Instance, Device) {
        let instance = Instance::new(InstanceCreateInfo {
            enable_validation: false,
            ..Default::default()
        })
        .unwrap();
        let physical_devices = instance.enumerate_physical_devices().unwrap();
        let physical_device = physical_devices[0];

        let graphics_family = unsafe {
            instance
                .get_physical_device_queue_family_properties(physical_device)
                .iter()
                .enumerate()
                .find(|(_, qf)| qf.queue_flags.contains(vk::QueueFlags::GRAPHICS))
                .map(|(i, _)| i as u32)
                .unwrap()
        };

        let device = Device::new(
            &instance,
            physical_device,
            DeviceCreateInfo {
                queue_create_infos: vec![QueueCreateInfo {
                    queue_family_index: graphics_family,
                    queue_count: 1,
                    queue_priorities: vec![1.0],
                }],
                ..Default::default()
            },
        )
        .unwrap();

        (instance, device)
    }

    #[test]
    fn test_fence_creation() {
        let (_instance, device) = create_test_device();
        let fence = Fence::new(&device, false).unwrap();
        assert_ne!(fence.handle(), vk::Fence::null());
        fence.destroy(&device); // Manual cleanup
    }

    #[test]
    fn test_fence_signaled() {
        let (_instance, device) = create_test_device();
        let fence = Fence::new(&device, true).unwrap();

        // Should immediately succeed since it starts signaled
        assert!(fence.wait(&device, 0).is_ok());

        fence.destroy(&device);
    }

    #[test]
    fn test_fence_reset() {
        let (_instance, device) = create_test_device();
        let fence = Fence::new(&device, true).unwrap();

        // Reset and verify it times out (not signaled)
        fence.reset(&device).unwrap();
        assert!(fence.wait(&device, 0).is_err()); // Should timeout

        fence.destroy(&device);
    }

    #[test]
    fn test_semaphore_creation() {
        let (_instance, device) = create_test_device();
        let semaphore = Semaphore::new(&device).unwrap();
        assert_ne!(semaphore.handle(), vk::Semaphore::null());
        semaphore.destroy(&device); // Manual cleanup
    }
}