// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use crate::{
    device::{Device, DeviceOwned, Queue},
    macros::{vulkan_bitflags, vulkan_enum},
    OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use parking_lot::{Mutex, MutexGuard};
use smallvec::SmallVec;
#[cfg(unix)]
use std::fs::File;
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    future::Future,
    mem::MaybeUninit,
    num::NonZeroU64,
    pin::Pin,
    ptr,
    sync::{Arc, Weak},
    task::{Context, Poll},
    time::Duration,
};

/// A two-state synchronization primitive that is signalled by the device and waited on by the host.
///
/// # Queue-to-host synchronization
///
/// The primary use of a fence is to know when execution of a queue has reached a particular point.
/// When adding a command to a queue, a fence can be provided with the command, to be signaled
/// when the operation finishes. You can check for a fence's current status by calling
/// `is_signaled`, `wait` or `await` on it. If the fence is found to be signaled, that means that
/// the queue has completed the operation that is associated with the fence, and all operations that
/// were submitted before it have been completed as well.
///
/// When a queue command accesses a resource, it must be kept alive until the queue command has
/// finished executing, and you may not be allowed to perform certain other operations (or even any)
/// while the resource is in use. By calling `is_signaled`, `wait` or `await`, the queue will be
/// notified when the fence is signaled, so that all resources of the associated queue operation and
/// preceding operations can be released.
///
/// Because of this, it is highly recommended to call `is_signaled`, `wait` or `await` on your fences.
/// Otherwise, the queue will hold onto resources indefinitely (using up memory)
/// and resource locks will not be released, which may cause errors when submitting future
/// queue operations. It is not strictly necessary to wait for *every* fence, as a fence
/// that was signaled later in the queue will automatically clean up resources associated with
/// earlier fences too.
#[derive(Debug)]
pub struct Fence {
    handle: ash::vk::Fence,
    device: Arc<Device>,
    id: NonZeroU64,
    must_put_in_pool: bool,

    export_handle_types: ExternalFenceHandleTypes,

    state: Mutex<FenceState>,
}

impl Fence {
    /// Creates a new `Fence`.
    #[inline]
    pub fn new(device: Arc<Device>, create_info: FenceCreateInfo) -> Result<Fence, FenceError> {
        Self::validate_new(&device, &create_info)?;

        unsafe { Ok(Self::new_unchecked(device, create_info)?) }
    }

    fn validate_new(device: &Device, create_info: &FenceCreateInfo) -> Result<(), FenceError> {
        let &FenceCreateInfo {
            signaled: _,
            export_handle_types,
            _ne: _,
        } = create_info;

        if !export_handle_types.is_empty() {
            if !(device.api_version() >= Version::V1_1
                || device.enabled_extensions().khr_external_fence)
            {
                return Err(FenceError::RequirementNotMet {
                    required_for: "`create_info.export_handle_types` is not empty",
                    requires_one_of: RequiresOneOf {
                        api_version: Some(Version::V1_1),
                        device_extensions: &["khr_external_fence"],
                        ..Default::default()
                    },
                });
            }

            // VUID-VkExportFenceCreateInfo-handleTypes-01446
            export_handle_types.validate_device(device)?;

            // VUID-VkExportFenceCreateInfo-handleTypes-01446
            for handle_type in export_handle_types.into_iter() {
                let external_fence_properties = unsafe {
                    device
                        .physical_device()
                        .external_fence_properties_unchecked(ExternalFenceInfo::handle_type(
                            handle_type,
                        ))
                };

                if !external_fence_properties.exportable {
                    return Err(FenceError::HandleTypeNotExportable { handle_type });
                }

                if !external_fence_properties
                    .compatible_handle_types
                    .contains(&export_handle_types)
                {
                    return Err(FenceError::ExportHandleTypesNotCompatible);
                }
            }
        }

        Ok(())
    }

    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn new_unchecked(
        device: Arc<Device>,
        create_info: FenceCreateInfo,
    ) -> Result<Fence, VulkanError> {
        let FenceCreateInfo {
            signaled,
            export_handle_types,
            _ne: _,
        } = create_info;

        let mut flags = ash::vk::FenceCreateFlags::empty();

        if signaled {
            flags |= ash::vk::FenceCreateFlags::SIGNALED;
        }

        let mut create_info_vk = ash::vk::FenceCreateInfo {
            flags,
            ..Default::default()
        };
        let mut export_fence_create_info_vk = None;

        if !export_handle_types.is_empty() {
            let _ = export_fence_create_info_vk.insert(ash::vk::ExportFenceCreateInfo {
                handle_types: export_handle_types.into(),
                ..Default::default()
            });
        }

        if let Some(info) = export_fence_create_info_vk.as_mut() {
            info.p_next = create_info_vk.p_next;
            create_info_vk.p_next = info as *const _ as *const _;
        }

        let handle = {
            let fns = device.fns();
            let mut output = MaybeUninit::uninit();
            (fns.v1_0.create_fence)(
                device.handle(),
                &create_info_vk,
                ptr::null(),
                output.as_mut_ptr(),
            )
            .result()
            .map_err(VulkanError::from)?;

            output.assume_init()
        };

        Ok(Fence {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
            export_handle_types,
            state: Mutex::new(FenceState {
                is_signaled: signaled,
                ..Default::default()
            }),
        })
    }

    /// Takes a fence from the vulkano-provided fence pool.
    /// If the pool is empty, a new fence will be created.
    /// Upon `drop`, the fence is put back into the pool.
    ///
    /// For most applications, using the fence pool should be preferred,
    /// in order to avoid creating new fences every frame.
    #[inline]
    pub fn from_pool(device: Arc<Device>) -> Result<Fence, FenceError> {
        let handle = device.fence_pool().lock().pop();
        let fence = match handle {
            Some(handle) => {
                unsafe {
                    // Make sure the fence isn't signaled
                    let fns = device.fns();
                    (fns.v1_0.reset_fences)(device.handle(), 1, &handle)
                        .result()
                        .map_err(VulkanError::from)?;
                }

                Fence {
                    handle,
                    device,
                    id: Self::next_id(),
                    must_put_in_pool: true,
                    export_handle_types: ExternalFenceHandleTypes::empty(),
                    state: Mutex::new(Default::default()),
                }
            }
            None => {
                // Pool is empty, alloc new fence
                let mut fence = Fence::new(device, FenceCreateInfo::default())?;
                fence.must_put_in_pool = true;
                fence
            }
        };

        Ok(fence)
    }

    /// Creates a new `Fence` from a raw object handle.
    ///
    /// # Safety
    ///
    /// - `handle` must be a valid Vulkan object handle created from `device`.
    /// - `create_info` must match the info used to create the object.
    #[inline]
    pub unsafe fn from_handle(
        device: Arc<Device>,
        handle: ash::vk::Fence,
        create_info: FenceCreateInfo,
    ) -> Fence {
        let FenceCreateInfo {
            signaled,
            export_handle_types,
            _ne: _,
        } = create_info;

        Fence {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
            export_handle_types,
            state: Mutex::new(FenceState {
                is_signaled: signaled,
                ..Default::default()
            }),
        }
    }

    /// Returns true if the fence is signaled.
    #[inline]
    pub fn is_signaled(&self) -> Result<bool, OomError> {
        let queue_to_signal = {
            let mut state = self.state();

            // If the fence is already signaled, or it's unsignaled but there's no queue that
            // could signal it, return the currently known value.
            if let Some(is_signaled) = state.is_signaled() {
                return Ok(is_signaled);
            }

            // We must ask Vulkan for the state.
            let result = unsafe {
                let fns = self.device.fns();
                (fns.v1_0.get_fence_status)(self.device.handle(), self.handle)
            };

            match result {
                ash::vk::Result::SUCCESS => unsafe { state.set_signaled() },
                ash::vk::Result::NOT_READY => return Ok(false),
                err => return Err(VulkanError::from(err).into()),
            }
        };

        // If we have a queue that we need to signal our status to,
        // do so now after the state lock is dropped, to avoid deadlocks.
        if let Some(queue) = queue_to_signal {
            unsafe {
                queue.with(|mut q| q.fence_signaled(self));
            }
        }

        Ok(true)
    }

    /// Waits until the fence is signaled, or at least until the timeout duration has elapsed.
    ///
    /// Returns `Ok` if the fence is now signaled. Returns `Err` if the timeout was reached instead.
    ///
    /// If you pass a duration of 0, then the function will return without blocking.
    pub fn wait(&self, timeout: Option<Duration>) -> Result<(), FenceError> {
        let queue_to_signal = {
            let mut state = self.state.lock();

            // If the fence is already signaled, we don't need to wait.
            if state.is_signaled().unwrap_or(false) {
                return Ok(());
            }

            let timeout_ns = timeout.map_or(u64::MAX, |timeout| {
                timeout
                    .as_secs()
                    .saturating_mul(1_000_000_000)
                    .saturating_add(timeout.subsec_nanos() as u64)
            });

            let result = unsafe {
                let fns = self.device.fns();
                (fns.v1_0.wait_for_fences)(
                    self.device.handle(),
                    1,
                    &self.handle,
                    ash::vk::TRUE,
                    timeout_ns,
                )
            };

            match result {
                ash::vk::Result::SUCCESS => unsafe { state.set_signaled() },
                ash::vk::Result::TIMEOUT => return Err(FenceError::Timeout),
                err => return Err(VulkanError::from(err).into()),
            }
        };

        // If we have a queue that we need to signal our status to,
        // do so now after the state lock is dropped, to avoid deadlocks.
        if let Some(queue) = queue_to_signal {
            unsafe {
                queue.with(|mut q| q.fence_signaled(self));
            }
        }

        Ok(())
    }

    /// Waits for multiple fences at once.
    ///
    /// # Panics
    ///
    /// - Panics if not all fences belong to the same device.
    pub fn multi_wait<'a>(
        fences: impl IntoIterator<Item = &'a Fence>,
        timeout: Option<Duration>,
    ) -> Result<(), FenceError> {
        let fences: SmallVec<[_; 8]> = fences.into_iter().collect();
        Self::validate_multi_wait(&fences, timeout)?;

        unsafe { Self::multi_wait_unchecked(fences, timeout) }
    }

    fn validate_multi_wait(
        fences: &[&Fence],
        _timeout: Option<Duration>,
    ) -> Result<(), FenceError> {
        if fences.is_empty() {
            return Ok(());
        }

        let device = &fences[0].device;

        for fence in fences {
            // VUID-vkWaitForFences-pFences-parent
            assert_eq!(device, &fence.device);
        }

        Ok(())
    }

    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    pub unsafe fn multi_wait_unchecked<'a>(
        fences: impl IntoIterator<Item = &'a Fence>,
        timeout: Option<Duration>,
    ) -> Result<(), FenceError> {
        let queues_to_signal: SmallVec<[_; 8]> = {
            let iter = fences.into_iter();
            let mut fences_vk: SmallVec<[_; 8]> = SmallVec::new();
            let mut fences: SmallVec<[_; 8]> = SmallVec::new();
            let mut states: SmallVec<[_; 8]> = SmallVec::new();

            for fence in iter {
                let state = fence.state.lock();

                // Skip the fences that are already signaled.
                if !state.is_signaled().unwrap_or(false) {
                    fences_vk.push(fence.handle);
                    fences.push(fence);
                    states.push(state);
                }
            }

            // VUID-vkWaitForFences-fenceCount-arraylength
            // If there are no fences, or all the fences are signaled, we don't need to wait.
            if fences_vk.is_empty() {
                return Ok(());
            }

            let device = &fences[0].device;
            let timeout_ns = timeout.map_or(u64::MAX, |timeout| {
                timeout
                    .as_secs()
                    .saturating_mul(1_000_000_000)
                    .saturating_add(timeout.subsec_nanos() as u64)
            });

            let result = {
                let fns = device.fns();
                (fns.v1_0.wait_for_fences)(
                    device.handle(),
                    fences_vk.len() as u32,
                    fences_vk.as_ptr(),
                    ash::vk::TRUE, // TODO: let the user choose false here?
                    timeout_ns,
                )
            };

            match result {
                ash::vk::Result::SUCCESS => fences
                    .into_iter()
                    .zip(&mut states)
                    .filter_map(|(fence, state)| state.set_signaled().map(|state| (state, fence)))
                    .collect(),
                ash::vk::Result::TIMEOUT => return Err(FenceError::Timeout),
                err => return Err(VulkanError::from(err).into()),
            }
        };

        // If we have queues that we need to signal our status to,
        // do so now after the state locks are dropped, to avoid deadlocks.
        for (queue, fence) in queues_to_signal {
            queue.with(|mut q| q.fence_signaled(fence));
        }

        Ok(())
    }

    /// Resets the fence.
    ///
    /// The fence must not be in use by a queue operation.
    #[inline]
    pub fn reset(&self) -> Result<(), FenceError> {
        let mut state = self.state.lock();
        self.validate_reset(&state)?;

        unsafe { Ok(self.reset_unchecked_locked(&mut state)?) }
    }

    fn validate_reset(&self, state: &FenceState) -> Result<(), FenceError> {
        // VUID-vkResetFences-pFences-01123
        if state.is_in_queue() {
            return Err(FenceError::InQueue);
        }

        Ok(())
    }

    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn reset_unchecked(&self) -> Result<(), VulkanError> {
        let mut state = self.state.lock();

        self.reset_unchecked_locked(&mut state)
    }

    unsafe fn reset_unchecked_locked(&self, state: &mut FenceState) -> Result<(), VulkanError> {
        let fns = self.device.fns();
        (fns.v1_0.reset_fences)(self.device.handle(), 1, &self.handle)
            .result()
            .map_err(VulkanError::from)?;

        state.reset();

        Ok(())
    }

    /// Resets multiple fences at once.
    ///
    /// The fences must not be in use by a queue operation.
    ///
    /// # Panics
    ///
    /// - Panics if not all fences belong to the same device.
    pub fn multi_reset<'a>(fences: impl IntoIterator<Item = &'a Fence>) -> Result<(), FenceError> {
        let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences
            .into_iter()
            .map(|fence| {
                let state = fence.state.lock();
                (fence, state)
            })
            .unzip();
        Self::validate_multi_reset(&fences, &states)?;

        unsafe { Ok(Self::multi_reset_unchecked_locked(&fences, &mut states)?) }
    }

    fn validate_multi_reset(
        fences: &[&Fence],
        states: &[MutexGuard<'_, FenceState>],
    ) -> Result<(), FenceError> {
        if fences.is_empty() {
            return Ok(());
        }

        let device = &fences[0].device;

        for (fence, state) in fences.iter().zip(states) {
            // VUID-vkResetFences-pFences-parent
            assert_eq!(device, &fence.device);

            // VUID-vkResetFences-pFences-01123
            if state.is_in_queue() {
                return Err(FenceError::InQueue);
            }
        }

        Ok(())
    }

    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    pub unsafe fn multi_reset_unchecked<'a>(
        fences: impl IntoIterator<Item = &'a Fence>,
    ) -> Result<(), VulkanError> {
        let (fences, mut states): (SmallVec<[_; 8]>, SmallVec<[_; 8]>) = fences
            .into_iter()
            .map(|fence| {
                let state = fence.state.lock();
                (fence, state)
            })
            .unzip();

        Self::multi_reset_unchecked_locked(&fences, &mut states)
    }

    unsafe fn multi_reset_unchecked_locked(
        fences: &[&Fence],
        states: &mut [MutexGuard<'_, FenceState>],
    ) -> Result<(), VulkanError> {
        if fences.is_empty() {
            return Ok(());
        }

        let device = &fences[0].device;
        let fences_vk: SmallVec<[_; 8]> = fences.iter().map(|fence| fence.handle).collect();

        let fns = device.fns();
        (fns.v1_0.reset_fences)(device.handle(), fences_vk.len() as u32, fences_vk.as_ptr())
            .result()
            .map_err(VulkanError::from)?;

        for state in states {
            state.reset();
        }

        Ok(())
    }

    /// Exports the fence into a POSIX file descriptor. The caller owns the returned `File`.
    ///
    /// The [`khr_external_fence_fd`](crate::device::DeviceExtensions::khr_external_fence_fd)
    /// extension must be enabled on the device.
    #[cfg(unix)]
    #[inline]
    pub fn export_fd(&self, handle_type: ExternalFenceHandleType) -> Result<File, FenceError> {
        let mut state = self.state.lock();
        self.validate_export_fd(handle_type, &state)?;

        unsafe { Ok(self.export_fd_unchecked_locked(handle_type, &mut state)?) }
    }

    #[cfg(unix)]
    fn validate_export_fd(
        &self,
        handle_type: ExternalFenceHandleType,
        state: &FenceState,
    ) -> Result<(), FenceError> {
        if !self.device.enabled_extensions().khr_external_fence_fd {
            return Err(FenceError::RequirementNotMet {
                required_for: "`export_fd`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_fence_fd"],
                    ..Default::default()
                },
            });
        }

        // VUID-VkFenceGetFdInfoKHR-handleType-parameter
        handle_type.validate_device(&self.device)?;

        // VUID-VkFenceGetFdInfoKHR-handleType-01453
        if !self.export_handle_types.intersects(&handle_type.into()) {
            return Err(FenceError::HandleTypeNotEnabled);
        }

        // VUID-VkFenceGetFdInfoKHR-handleType-01454
        if handle_type.has_copy_transference()
            && !(state.is_signaled().unwrap_or(false) || state.is_in_queue())
        {
            return Err(FenceError::HandleTypeCopyNotSignaled);
        }

        // VUID-VkFenceGetFdInfoKHR-fence-01455
        if let Some(imported_handle_type) = state.current_import {
            match imported_handle_type {
                ImportType::SwapchainAcquire => {
                    return Err(FenceError::ImportedForSwapchainAcquire)
                }
                ImportType::ExternalFence(imported_handle_type) => {
                    let external_fence_properties = unsafe {
                        self.device
                            .physical_device()
                            .external_fence_properties_unchecked(ExternalFenceInfo::handle_type(
                                handle_type,
                            ))
                    };

                    if !external_fence_properties
                        .export_from_imported_handle_types
                        .intersects(&imported_handle_type.into())
                    {
                        return Err(FenceError::ExportFromImportedNotSupported {
                            imported_handle_type,
                        });
                    }
                }
            }
        }

        // VUID-VkFenceGetFdInfoKHR-handleType-01456
        if !matches!(
            handle_type,
            ExternalFenceHandleType::OpaqueFd | ExternalFenceHandleType::SyncFd
        ) {
            return Err(FenceError::HandleTypeNotFd);
        }

        Ok(())
    }

    #[cfg(unix)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn export_fd_unchecked(
        &self,
        handle_type: ExternalFenceHandleType,
    ) -> Result<File, VulkanError> {
        let mut state = self.state.lock();
        self.export_fd_unchecked_locked(handle_type, &mut state)
    }

    #[cfg(unix)]
    unsafe fn export_fd_unchecked_locked(
        &self,
        handle_type: ExternalFenceHandleType,
        state: &mut FenceState,
    ) -> Result<File, VulkanError> {
        use std::os::unix::io::FromRawFd;

        let info_vk = ash::vk::FenceGetFdInfoKHR {
            fence: self.handle,
            handle_type: handle_type.into(),
            ..Default::default()
        };

        let mut output = MaybeUninit::uninit();
        let fns = self.device.fns();
        (fns.khr_external_fence_fd.get_fence_fd_khr)(
            self.device.handle(),
            &info_vk,
            output.as_mut_ptr(),
        )
        .result()
        .map_err(VulkanError::from)?;

        state.export(handle_type);

        Ok(File::from_raw_fd(output.assume_init()))
    }

    /// Exports the fence into a Win32 handle.
    ///
    /// The [`khr_external_fence_win32`](crate::device::DeviceExtensions::khr_external_fence_win32)
    /// extension must be enabled on the device.
    #[cfg(windows)]
    #[inline]
    pub fn export_win32_handle(
        &self,
        handle_type: ExternalFenceHandleType,
    ) -> Result<*mut std::ffi::c_void, FenceError> {
        let mut state = self.state.lock();
        self.validate_export_win32_handle(handle_type, &state)?;

        unsafe { Ok(self.export_win32_handle_unchecked_locked(handle_type, &mut state)?) }
    }

    #[cfg(windows)]
    fn validate_export_win32_handle(
        &self,
        handle_type: ExternalFenceHandleType,
        state: &FenceState,
    ) -> Result<(), FenceError> {
        if !self.device.enabled_extensions().khr_external_fence_win32 {
            return Err(FenceError::RequirementNotMet {
                required_for: "`export_win32_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_fence_win32"],
                    ..Default::default()
                },
            });
        }

        // VUID-VkFenceGetWin32HandleInfoKHR-handleType-parameter
        handle_type.validate_device(&self.device)?;

        // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01448
        if !self.export_handle_types.intersects(&handle_type.into()) {
            return Err(FenceError::HandleTypeNotEnabled);
        }

        // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01449
        if matches!(handle_type, ExternalFenceHandleType::OpaqueWin32)
            && state.is_exported(handle_type)
        {
            return Err(FenceError::AlreadyExported);
        }

        // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01451
        if handle_type.has_copy_transference()
            && !(state.is_signaled().unwrap_or(false) || state.is_in_queue())
        {
            return Err(FenceError::HandleTypeCopyNotSignaled);
        }

        // VUID-VkFenceGetWin32HandleInfoKHR-fence-01450
        if let Some(imported_handle_type) = state.current_import {
            match imported_handle_type {
                ImportType::SwapchainAcquire => {
                    return Err(FenceError::ImportedForSwapchainAcquire)
                }
                ImportType::ExternalFence(imported_handle_type) => {
                    let external_fence_properties = unsafe {
                        self.device
                            .physical_device()
                            .external_fence_properties_unchecked(ExternalFenceInfo::handle_type(
                                handle_type,
                            ))
                    };

                    if !external_fence_properties
                        .export_from_imported_handle_types
                        .intersects(&imported_handle_type.into())
                    {
                        return Err(FenceError::ExportFromImportedNotSupported {
                            imported_handle_type,
                        });
                    }
                }
            }
        }

        // VUID-VkFenceGetWin32HandleInfoKHR-handleType-01452
        if !matches!(
            handle_type,
            ExternalFenceHandleType::OpaqueWin32 | ExternalFenceHandleType::OpaqueWin32Kmt
        ) {
            return Err(FenceError::HandleTypeNotWin32);
        }

        Ok(())
    }

    #[cfg(windows)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn export_win32_handle_unchecked(
        &self,
        handle_type: ExternalFenceHandleType,
    ) -> Result<*mut std::ffi::c_void, VulkanError> {
        let mut state = self.state.lock();
        self.export_win32_handle_unchecked_locked(handle_type, &mut state)
    }

    #[cfg(windows)]
    unsafe fn export_win32_handle_unchecked_locked(
        &self,
        handle_type: ExternalFenceHandleType,
        state: &mut FenceState,
    ) -> Result<*mut std::ffi::c_void, VulkanError> {
        let info_vk = ash::vk::FenceGetWin32HandleInfoKHR {
            fence: self.handle,
            handle_type: handle_type.into(),
            ..Default::default()
        };

        let mut output = MaybeUninit::uninit();
        let fns = self.device.fns();
        (fns.khr_external_fence_win32.get_fence_win32_handle_khr)(
            self.device.handle(),
            &info_vk,
            output.as_mut_ptr(),
        )
        .result()
        .map_err(VulkanError::from)?;

        state.export(handle_type);

        Ok(output.assume_init())
    }

    /// Imports a fence from a POSIX file descriptor.
    ///
    /// The [`khr_external_fence_fd`](crate::device::DeviceExtensions::khr_external_fence_fd)
    /// extension must be enabled on the device.
    ///
    /// # Safety
    ///
    /// - If in `import_fence_fd_info`, `handle_type` is `ExternalHandleType::OpaqueFd`,
    ///   then `file` must represent a fence that was exported from Vulkan or a compatible API,
    ///   with a driver and device UUID equal to those of the device that owns `self`.
    #[cfg(unix)]
    #[inline]
    pub unsafe fn import_fd(
        &self,
        import_fence_fd_info: ImportFenceFdInfo,
    ) -> Result<(), FenceError> {
        let mut state = self.state.lock();
        self.validate_import_fd(&import_fence_fd_info, &state)?;

        Ok(self.import_fd_unchecked_locked(import_fence_fd_info, &mut state)?)
    }

    #[cfg(unix)]
    fn validate_import_fd(
        &self,
        import_fence_fd_info: &ImportFenceFdInfo,
        state: &FenceState,
    ) -> Result<(), FenceError> {
        if !self.device.enabled_extensions().khr_external_fence_fd {
            return Err(FenceError::RequirementNotMet {
                required_for: "`import_fd`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_fence_fd"],
                    ..Default::default()
                },
            });
        }

        // VUID-vkImportFenceFdKHR-fence-01463
        if state.is_in_queue() {
            return Err(FenceError::InQueue);
        }

        let &ImportFenceFdInfo {
            flags,
            handle_type,
            file: _,
            _ne: _,
        } = import_fence_fd_info;

        // VUID-VkImportFenceFdInfoKHR-flags-parameter
        flags.validate_device(&self.device)?;

        // VUID-VkImportFenceFdInfoKHR-handleType-parameter
        handle_type.validate_device(&self.device)?;

        // VUID-VkImportFenceFdInfoKHR-handleType-01464
        if !matches!(
            handle_type,
            ExternalFenceHandleType::OpaqueFd | ExternalFenceHandleType::SyncFd
        ) {
            return Err(FenceError::HandleTypeNotFd);
        }

        // VUID-VkImportFenceFdInfoKHR-fd-01541
        // Can't validate, therefore unsafe

        // VUID-VkImportFenceFdInfoKHR-handleType-07306
        if handle_type.has_copy_transference() && !flags.temporary {
            return Err(FenceError::HandletypeCopyNotTemporary);
        }

        Ok(())
    }

    #[cfg(unix)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn import_fd_unchecked(
        &self,
        import_fence_fd_info: ImportFenceFdInfo,
    ) -> Result<(), VulkanError> {
        let mut state = self.state.lock();
        self.import_fd_unchecked_locked(import_fence_fd_info, &mut state)
    }

    #[cfg(unix)]
    unsafe fn import_fd_unchecked_locked(
        &self,
        import_fence_fd_info: ImportFenceFdInfo,
        state: &mut FenceState,
    ) -> Result<(), VulkanError> {
        use std::os::unix::io::IntoRawFd;

        let ImportFenceFdInfo {
            flags,
            handle_type,
            file,
            _ne: _,
        } = import_fence_fd_info;

        let info_vk = ash::vk::ImportFenceFdInfoKHR {
            fence: self.handle,
            flags: flags.into(),
            handle_type: handle_type.into(),
            fd: file.map_or(-1, |file| file.into_raw_fd()),
            ..Default::default()
        };

        let fns = self.device.fns();
        (fns.khr_external_fence_fd.import_fence_fd_khr)(self.device.handle(), &info_vk)
            .result()
            .map_err(VulkanError::from)?;

        state.import(handle_type, flags.temporary);

        Ok(())
    }

    /// Imports a fence from a Win32 handle.
    ///
    /// The [`khr_external_fence_win32`](crate::device::DeviceExtensions::khr_external_fence_win32)
    /// extension must be enabled on the device.
    ///
    /// # Safety
    ///
    /// - In `import_fence_win32_handle_info`, `handle` must represent a fence that was exported
    ///   from Vulkan or a compatible API, with a driver and device UUID equal to those of the
    ///   device that owns `self`.
    #[cfg(windows)]
    #[inline]
    pub unsafe fn import_win32_handle(
        &self,
        import_fence_win32_handle_info: ImportFenceWin32HandleInfo,
    ) -> Result<(), FenceError> {
        let mut state = self.state.lock();
        self.validate_import_win32_handle(&import_fence_win32_handle_info, &state)?;

        Ok(self.import_win32_handle_unchecked_locked(import_fence_win32_handle_info, &mut state)?)
    }

    #[cfg(windows)]
    fn validate_import_win32_handle(
        &self,
        import_fence_win32_handle_info: &ImportFenceWin32HandleInfo,
        state: &FenceState,
    ) -> Result<(), FenceError> {
        if !self.device.enabled_extensions().khr_external_fence_win32 {
            return Err(FenceError::RequirementNotMet {
                required_for: "`import_win32_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_fence_win32"],
                    ..Default::default()
                },
            });
        }

        // VUID-vkImportFenceWin32HandleKHR-fence-04448
        if state.is_in_queue() {
            return Err(FenceError::InQueue);
        }

        let &ImportFenceWin32HandleInfo {
            flags,
            handle_type,
            handle: _,
            _ne: _,
        } = import_fence_win32_handle_info;

        // VUID-VkImportFenceWin32HandleInfoKHR-flags-parameter
        flags.validate_device(&self.device)?;

        // VUID-VkImportFenceWin32HandleInfoKHR-handleType-01457
        handle_type.validate_device(&self.device)?;

        // VUID-VkImportFenceWin32HandleInfoKHR-handleType-01457
        if !matches!(
            handle_type,
            ExternalFenceHandleType::OpaqueWin32 | ExternalFenceHandleType::OpaqueWin32Kmt
        ) {
            return Err(FenceError::HandleTypeNotWin32);
        }

        // VUID-VkImportFenceWin32HandleInfoKHR-handle-01539
        // Can't validate, therefore unsafe

        // VUID?
        if handle_type.has_copy_transference() && !flags.temporary {
            return Err(FenceError::HandletypeCopyNotTemporary);
        }

        Ok(())
    }

    #[cfg(windows)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn import_win32_handle_unchecked(
        &self,
        import_fence_win32_handle_info: ImportFenceWin32HandleInfo,
    ) -> Result<(), VulkanError> {
        let mut state = self.state.lock();
        self.import_win32_handle_unchecked_locked(import_fence_win32_handle_info, &mut state)
    }

    #[cfg(windows)]
    unsafe fn import_win32_handle_unchecked_locked(
        &self,
        import_fence_win32_handle_info: ImportFenceWin32HandleInfo,
        state: &mut FenceState,
    ) -> Result<(), VulkanError> {
        let ImportFenceWin32HandleInfo {
            flags,
            handle_type,
            handle,
            _ne: _,
        } = import_fence_win32_handle_info;

        let info_vk = ash::vk::ImportFenceWin32HandleInfoKHR {
            fence: self.handle,
            flags: flags.into(),
            handle_type: handle_type.into(),
            handle,
            name: ptr::null(), // TODO: support?
            ..Default::default()
        };

        let fns = self.device.fns();
        (fns.khr_external_fence_win32.import_fence_win32_handle_khr)(
            self.device.handle(),
            &info_vk,
        )
        .result()
        .map_err(VulkanError::from)?;

        state.import(handle_type, flags.temporary);

        Ok(())
    }

    pub(crate) fn state(&self) -> MutexGuard<'_, FenceState> {
        self.state.lock()
    }

    // Shared by Fence and FenceSignalFuture
    pub(crate) fn poll_impl(&self, cx: &mut Context<'_>) -> Poll<Result<(), OomError>> {
        // Vulkan only allows polling of the fence status, so we have to use a spin future.
        // This is still better than blocking in async applications, since a smart-enough async engine
        // can choose to run some other tasks between probing this one.

        // Check if we are done without blocking
        match self.is_signaled() {
            Err(e) => return Poll::Ready(Err(e)),
            Ok(signalled) => {
                if signalled {
                    return Poll::Ready(Ok(()));
                }
            }
        }

        // Otherwise spin
        cx.waker().wake_by_ref();
        Poll::Pending
    }
}

impl Drop for Fence {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            if self.must_put_in_pool {
                let raw_fence = self.handle;
                self.device.fence_pool().lock().push(raw_fence);
            } else {
                let fns = self.device.fns();
                (fns.v1_0.destroy_fence)(self.device.handle(), self.handle, ptr::null());
            }
        }
    }
}

impl Future for Fence {
    type Output = Result<(), OomError>;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        self.poll_impl(cx)
    }
}

unsafe impl VulkanObject for Fence {
    type Handle = ash::vk::Fence;

    #[inline]
    fn handle(&self) -> Self::Handle {
        self.handle
    }
}

unsafe impl DeviceOwned for Fence {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

crate::impl_id_counter!(Fence);

#[derive(Debug, Default)]
pub(crate) struct FenceState {
    is_signaled: bool,
    pending_signal: Option<Weak<Queue>>,

    reference_exported: bool,
    exported_handle_types: ExternalFenceHandleTypes,
    current_import: Option<ImportType>,
    permanent_import: Option<ExternalFenceHandleType>,
}

impl FenceState {
    /// If the fence is not in a queue and has no external references, returns the current status.
    #[inline]
    fn is_signaled(&self) -> Option<bool> {
        // If either of these is true, we can't be certain of the status.
        if self.is_in_queue() || self.has_external_reference() {
            None
        } else {
            Some(self.is_signaled)
        }
    }

    #[inline]
    fn is_in_queue(&self) -> bool {
        self.pending_signal.is_some()
    }

    /// Returns whether there are any potential external references to the fence payload.
    /// That is, the fence has been exported by reference transference, or imported.
    #[inline]
    fn has_external_reference(&self) -> bool {
        self.reference_exported || self.current_import.is_some()
    }

    #[allow(dead_code)]
    #[inline]
    fn is_exported(&self, handle_type: ExternalFenceHandleType) -> bool {
        self.exported_handle_types.intersects(&handle_type.into())
    }

    #[inline]
    pub(crate) unsafe fn add_queue_signal(&mut self, queue: &Arc<Queue>) {
        self.pending_signal = Some(Arc::downgrade(queue));
    }

    /// Called when a fence first discovers that it is signaled.
    /// Returns the queue that should be informed about it.
    #[inline]
    unsafe fn set_signaled(&mut self) -> Option<Arc<Queue>> {
        self.is_signaled = true;

        // Fences with external references can't be used to determine queue completion.
        if self.has_external_reference() {
            self.pending_signal = None;
            None
        } else {
            self.pending_signal.take().and_then(|queue| queue.upgrade())
        }
    }

    /// Called when a queue is unlocking resources.
    #[inline]
    pub(crate) unsafe fn set_signal_finished(&mut self) {
        self.is_signaled = true;
        self.pending_signal = None;
    }

    #[inline]
    unsafe fn reset(&mut self) {
        debug_assert!(!self.is_in_queue());
        self.current_import = self.permanent_import.map(Into::into);
        self.is_signaled = false;
    }

    #[allow(dead_code)]
    #[inline]
    unsafe fn export(&mut self, handle_type: ExternalFenceHandleType) {
        self.exported_handle_types |= handle_type.into();

        if handle_type.has_copy_transference() {
            self.reset();
        } else {
            self.reference_exported = true;
        }
    }

    #[allow(dead_code)]
    #[inline]
    unsafe fn import(&mut self, handle_type: ExternalFenceHandleType, temporary: bool) {
        debug_assert!(!self.is_in_queue());
        self.current_import = Some(handle_type.into());

        if !temporary {
            self.permanent_import = Some(handle_type);
        }
    }

    #[inline]
    pub(crate) unsafe fn import_swapchain_acquire(&mut self) {
        debug_assert!(!self.is_in_queue());
        self.current_import = Some(ImportType::SwapchainAcquire);
    }
}

#[derive(Clone, Copy, Debug)]
enum ImportType {
    SwapchainAcquire,
    ExternalFence(ExternalFenceHandleType),
}

impl From<ExternalFenceHandleType> for ImportType {
    #[inline]
    fn from(handle_type: ExternalFenceHandleType) -> Self {
        Self::ExternalFence(handle_type)
    }
}

/// Parameters to create a new `Fence`.
#[derive(Clone, Debug)]
pub struct FenceCreateInfo {
    /// Whether the fence should be created in the signaled state.
    ///
    /// The default value is `false`.
    pub signaled: bool,

    /// The handle types that can be exported from the fence.
    pub export_handle_types: ExternalFenceHandleTypes,

    pub _ne: crate::NonExhaustive,
}

impl Default for FenceCreateInfo {
    #[inline]
    fn default() -> Self {
        Self {
            signaled: false,
            export_handle_types: ExternalFenceHandleTypes::empty(),
            _ne: crate::NonExhaustive(()),
        }
    }
}

vulkan_enum! {
    /// The handle type used for Vulkan external fence APIs.
    #[non_exhaustive]
    ExternalFenceHandleType = ExternalFenceHandleTypeFlags(u32);

    /// A POSIX file descriptor handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    OpaqueFd = OPAQUE_FD,

    /// A Windows NT handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    OpaqueWin32 = OPAQUE_WIN32,

    /// A Windows global share handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    OpaqueWin32Kmt = OPAQUE_WIN32_KMT,

    /// A POSIX file descriptor handle to a Linux Sync File or Android Fence object.
    ///
    /// This handle type has *copy transference*.
    SyncFd = SYNC_FD,
}

impl ExternalFenceHandleType {
    /// Returns whether the given handle type has *copy transference* rather than *reference
    /// transference*.
    ///
    /// Imports of handles with copy transference must always be temporary. Exports of such
    /// handles must only occur if the fence is already signaled, or if there is a fence signal
    /// operation pending in a queue.
    #[inline]
    pub fn has_copy_transference(&self) -> bool {
        // As defined by
        // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-fence-handletypes-win32
        // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-fence-handletypes-fd
        matches!(self, Self::SyncFd)
    }
}

vulkan_bitflags! {
    /// A mask of multiple external fence handle types.
    #[non_exhaustive]
    ExternalFenceHandleTypes = ExternalFenceHandleTypeFlags(u32);

    /// A POSIX file descriptor handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    opaque_fd = OPAQUE_FD,

    /// A Windows NT handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    opaque_win32 = OPAQUE_WIN32,

    /// A Windows global share handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    opaque_win32_kmt = OPAQUE_WIN32_KMT,

    /// A POSIX file descriptor handle to a Linux Sync File or Android Fence object.
    ///
    /// This handle type has *copy transference*.
    sync_fd = SYNC_FD,
}

impl From<ExternalFenceHandleType> for ExternalFenceHandleTypes {
    #[inline]
    fn from(val: ExternalFenceHandleType) -> Self {
        let mut result = Self::empty();

        match val {
            ExternalFenceHandleType::OpaqueFd => result.opaque_fd = true,
            ExternalFenceHandleType::OpaqueWin32 => result.opaque_win32 = true,
            ExternalFenceHandleType::OpaqueWin32Kmt => result.opaque_win32_kmt = true,
            ExternalFenceHandleType::SyncFd => result.sync_fd = true,
        }

        result
    }
}

impl ExternalFenceHandleTypes {
    fn into_iter(self) -> impl IntoIterator<Item = ExternalFenceHandleType> {
        let Self {
            opaque_fd,
            opaque_win32,
            opaque_win32_kmt,
            sync_fd,
            _ne: _,
        } = self;

        [
            opaque_fd.then_some(ExternalFenceHandleType::OpaqueFd),
            opaque_win32.then_some(ExternalFenceHandleType::OpaqueWin32),
            opaque_win32_kmt.then_some(ExternalFenceHandleType::OpaqueWin32Kmt),
            sync_fd.then_some(ExternalFenceHandleType::SyncFd),
        ]
        .into_iter()
        .flatten()
    }
}

vulkan_bitflags! {
    /// Additional parameters for a fence payload import.
    #[non_exhaustive]
    FenceImportFlags = FenceImportFlags(u32);

    /// The fence payload will be imported only temporarily, regardless of the permanence of the
    /// imported handle type.
    temporary = TEMPORARY,
}

#[cfg(unix)]
#[derive(Debug)]
pub struct ImportFenceFdInfo {
    /// Additional parameters for the import operation.
    ///
    /// If `handle_type` has *copy transference*, this must include the `temporary` flag.
    ///
    /// The default value is [`FenceImportFlags::empty()`].
    pub flags: FenceImportFlags,

    /// The handle type of `file`.
    ///
    /// There is no default value.
    pub handle_type: ExternalFenceHandleType,

    /// The file to import the fence from.
    ///
    /// If `handle_type` is `ExternalFenceHandleType::SyncFd`, then `file` can be `None`.
    /// Instead of an imported file descriptor, a dummy file descriptor `-1` is used,
    /// which represents a fence that is always signaled.
    ///
    /// The default value is `None`, which must be overridden if `handle_type` is not
    /// `ExternalFenceHandleType::SyncFd`.
    pub file: Option<File>,

    pub _ne: crate::NonExhaustive,
}

#[cfg(unix)]
impl ImportFenceFdInfo {
    /// Returns an `ImportFenceFdInfo` with the specified `handle_type`.
    #[inline]
    pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self {
        Self {
            flags: FenceImportFlags::empty(),
            handle_type,
            file: None,
            _ne: crate::NonExhaustive(()),
        }
    }
}

#[cfg(windows)]
#[derive(Debug)]
pub struct ImportFenceWin32HandleInfo {
    /// Additional parameters for the import operation.
    ///
    /// If `handle_type` has *copy transference*, this must include the `temporary` flag.
    ///
    /// The default value is [`FenceImportFlags::empty()`].
    pub flags: FenceImportFlags,

    /// The handle type of `handle`.
    ///
    /// There is no default value.
    pub handle_type: ExternalFenceHandleType,

    /// The file to import the fence from.
    ///
    /// The default value is `null`, which must be overridden.
    pub handle: *mut std::ffi::c_void,

    pub _ne: crate::NonExhaustive,
}

#[cfg(windows)]
impl ImportFenceWin32HandleInfo {
    /// Returns an `ImportFenceWin32HandleInfo` with the specified `handle_type`.
    #[inline]
    pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self {
        Self {
            flags: FenceImportFlags::empty(),
            handle_type,
            handle: ptr::null_mut(),
            _ne: crate::NonExhaustive(()),
        }
    }
}

/// The fence configuration to query in
/// [`PhysicalDevice::external_fence_properties`](crate::device::physical::PhysicalDevice::external_fence_properties).
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct ExternalFenceInfo {
    /// The external handle type that will be used with the fence.
    pub handle_type: ExternalFenceHandleType,

    pub _ne: crate::NonExhaustive,
}

impl ExternalFenceInfo {
    /// Returns an `ExternalFenceInfo` with the specified `handle_type`.
    #[inline]
    pub fn handle_type(handle_type: ExternalFenceHandleType) -> Self {
        Self {
            handle_type,
            _ne: crate::NonExhaustive(()),
        }
    }
}

/// The properties for exporting or importing external handles, when a fence is created
/// with a specific configuration.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ExternalFenceProperties {
    /// Whether a handle can be exported to an external source with the queried
    /// external handle type.
    pub exportable: bool,

    /// Whether a handle can be imported from an external source with the queried
    /// external handle type.
    pub importable: bool,

    /// Which external handle types can be re-exported after the queried external handle type has
    /// been imported.
    pub export_from_imported_handle_types: ExternalFenceHandleTypes,

    /// Which external handle types can be enabled along with the queried external handle type
    /// when creating the fence.
    pub compatible_handle_types: ExternalFenceHandleTypes,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum FenceError {
    /// Not enough memory available.
    OomError(OomError),

    /// The device has been lost.
    DeviceLost,

    /// The specified timeout wasn't long enough.
    Timeout,

    RequirementNotMet {
        required_for: &'static str,
        requires_one_of: RequiresOneOf,
    },

    /// The provided handle type does not permit more than one export,
    /// and a handle of this type was already exported previously.
    AlreadyExported,

    /// The provided handle type cannot be exported from the current import handle type.
    ExportFromImportedNotSupported {
        imported_handle_type: ExternalFenceHandleType,
    },

    /// One of the export handle types is not compatible with the other provided handles.
    ExportHandleTypesNotCompatible,

    /// A handle type with copy transference was provided, but the fence is not signaled and there
    /// is no pending queue operation that will signal it.
    HandleTypeCopyNotSignaled,

    /// A handle type with copy transference was provided,
    /// but the `temporary` import flag was not set.
    HandletypeCopyNotTemporary,

    /// The provided export handle type was not set in `export_handle_types` when creating the
    /// fence.
    HandleTypeNotEnabled,

    /// Exporting is not supported for the provided handle type.
    HandleTypeNotExportable {
        handle_type: ExternalFenceHandleType,
    },

    /// The provided handle type is not a POSIX file descriptor handle.
    HandleTypeNotFd,

    /// The provided handle type is not a Win32 handle.
    HandleTypeNotWin32,

    /// The fence currently has a temporary import for a swapchain acquire operation.
    ImportedForSwapchainAcquire,

    /// The fence is currently in use by a queue.
    InQueue,
}

impl Error for FenceError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::OomError(err) => Some(err),
            _ => None,
        }
    }
}

impl Display for FenceError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "not enough memory available"),
            Self::DeviceLost => write!(f, "the device was lost"),
            Self::Timeout => write!(f, "the timeout has been reached"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),

            Self::AlreadyExported => write!(
                f,
                "the provided handle type does not permit more than one export, and a handle of \
                this type was already exported previously",
            ),
            Self::ExportFromImportedNotSupported {
                imported_handle_type,
            } => write!(
                f,
                "the provided handle type cannot be exported from the current imported handle type \
                {:?}",
                imported_handle_type,
            ),
            Self::ExportHandleTypesNotCompatible => write!(
                f,
                "one of the export handle types is not compatible with the other provided handles",
            ),
            Self::HandleTypeCopyNotSignaled => write!(
                f,
                "a handle type with copy transference was provided, but the fence is not signaled \
                and there is no pending queue operation that will signal it",
            ),
            Self::HandletypeCopyNotTemporary => write!(
                f,
                "a handle type with copy transference was provided, but the `temporary` \
                import flag was not set",
            ),
            Self::HandleTypeNotEnabled => write!(
                f,
                "the provided export handle type was not set in `export_handle_types` when \
                creating the fence",
            ),
            Self::HandleTypeNotExportable { handle_type } => write!(
                f,
                "exporting is not supported for handles of type {:?}",
                handle_type,
            ),
            Self::HandleTypeNotFd => write!(
                f,
                "the provided handle type is not a POSIX file descriptor handle",
            ),
            Self::HandleTypeNotWin32 => {
                write!(f, "the provided handle type is not a Win32 handle")
            }
            Self::ImportedForSwapchainAcquire => write!(
                f,
                "the fence currently has a temporary import for a swapchain acquire operation",
            ),
            Self::InQueue => write!(f, "the fence is currently in use by a queue"),
        }
    }
}

impl From<VulkanError> for FenceError {
    fn from(err: VulkanError) -> Self {
        match err {
            e @ VulkanError::OutOfHostMemory | e @ VulkanError::OutOfDeviceMemory => {
                Self::OomError(e.into())
            }
            VulkanError::DeviceLost => Self::DeviceLost,
            _ => panic!("unexpected error: {:?}", err),
        }
    }
}

impl From<OomError> for FenceError {
    fn from(err: OomError) -> Self {
        Self::OomError(err)
    }
}

impl From<RequirementNotMet> for FenceError {
    fn from(err: RequirementNotMet) -> Self {
        Self::RequirementNotMet {
            required_for: err.required_for,
            requires_one_of: err.requires_one_of,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        sync::{fence::FenceCreateInfo, Fence},
        VulkanObject,
    };
    use std::time::Duration;

    #[test]
    fn fence_create() {
        let (device, _) = gfx_dev_and_queue!();

        let fence = Fence::new(device, Default::default()).unwrap();
        assert!(!fence.is_signaled().unwrap());
    }

    #[test]
    fn fence_create_signaled() {
        let (device, _) = gfx_dev_and_queue!();

        let fence = Fence::new(
            device,
            FenceCreateInfo {
                signaled: true,
                ..Default::default()
            },
        )
        .unwrap();
        assert!(fence.is_signaled().unwrap());
    }

    #[test]
    fn fence_signaled_wait() {
        let (device, _) = gfx_dev_and_queue!();

        let fence = Fence::new(
            device,
            FenceCreateInfo {
                signaled: true,
                ..Default::default()
            },
        )
        .unwrap();
        fence.wait(Some(Duration::new(0, 10))).unwrap();
    }

    #[test]
    fn fence_reset() {
        let (device, _) = gfx_dev_and_queue!();

        let fence = Fence::new(
            device,
            FenceCreateInfo {
                signaled: true,
                ..Default::default()
            },
        )
        .unwrap();
        fence.reset().unwrap();
        assert!(!fence.is_signaled().unwrap());
    }

    #[test]
    fn multiwait_different_devices() {
        let (device1, _) = gfx_dev_and_queue!();
        let (device2, _) = gfx_dev_and_queue!();

        assert_should_panic!({
            let fence1 = Fence::new(
                device1.clone(),
                FenceCreateInfo {
                    signaled: true,
                    ..Default::default()
                },
            )
            .unwrap();
            let fence2 = Fence::new(
                device2.clone(),
                FenceCreateInfo {
                    signaled: true,
                    ..Default::default()
                },
            )
            .unwrap();

            let _ = Fence::multi_wait(
                [&fence1, &fence2].iter().cloned(),
                Some(Duration::new(0, 10)),
            );
        });
    }

    #[test]
    fn multireset_different_devices() {
        let (device1, _) = gfx_dev_and_queue!();
        let (device2, _) = gfx_dev_and_queue!();

        assert_should_panic!({
            let fence1 = Fence::new(
                device1.clone(),
                FenceCreateInfo {
                    signaled: true,
                    ..Default::default()
                },
            )
            .unwrap();
            let fence2 = Fence::new(
                device2.clone(),
                FenceCreateInfo {
                    signaled: true,
                    ..Default::default()
                },
            )
            .unwrap();

            let _ = Fence::multi_reset([&fence1, &fence2]);
        });
    }

    #[test]
    fn fence_pool() {
        let (device, _) = gfx_dev_and_queue!();

        assert_eq!(device.fence_pool().lock().len(), 0);
        let fence1_internal_obj = {
            let fence = Fence::from_pool(device.clone()).unwrap();
            assert_eq!(device.fence_pool().lock().len(), 0);
            fence.handle()
        };

        assert_eq!(device.fence_pool().lock().len(), 1);
        let fence2 = Fence::from_pool(device.clone()).unwrap();
        assert_eq!(device.fence_pool().lock().len(), 0);
        assert_eq!(fence2.handle(), fence1_internal_obj);
    }
}
