// 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.

//! A semaphore provides synchronization between multiple queues, with non-command buffer
//! commands on the same queue, or between the device and an external source.

use crate::{
    device::{Device, DeviceOwned, Queue},
    macros::{impl_id_counter, vulkan_bitflags, vulkan_bitflags_enum},
    OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use parking_lot::{Mutex, MutexGuard};
#[cfg(unix)]
use std::fs::File;
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    mem::MaybeUninit,
    num::NonZeroU64,
    ptr,
    sync::{Arc, Weak},
};

/// Used to provide synchronization between command buffers during their execution.
///
/// It is similar to a fence, except that it is purely on the GPU side. The CPU can't query a
/// semaphore's status or wait for it to be signaled.
#[derive(Debug)]
pub struct Semaphore {
    handle: ash::vk::Semaphore,
    device: Arc<Device>,
    id: NonZeroU64,
    must_put_in_pool: bool,

    export_handle_types: ExternalSemaphoreHandleTypes,

    state: Mutex<SemaphoreState>,
}

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

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

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

        if !export_handle_types.is_empty() {
            if !(device.api_version() >= Version::V1_1
                || device.enabled_extensions().khr_external_semaphore)
            {
                return Err(SemaphoreError::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_semaphore"],
                        ..Default::default()
                    },
                });
            }

            // VUID-VkExportSemaphoreCreateInfo-handleTypes-parameter
            export_handle_types.validate_device(device)?;

            // VUID-VkExportSemaphoreCreateInfo-handleTypes-01124
            for handle_type in export_handle_types.into_iter() {
                let external_semaphore_properties = unsafe {
                    device
                        .physical_device()
                        .external_semaphore_properties_unchecked(
                            ExternalSemaphoreInfo::handle_type(handle_type),
                        )
                };

                if !external_semaphore_properties.exportable {
                    return Err(SemaphoreError::HandleTypeNotExportable { handle_type });
                }

                if !external_semaphore_properties
                    .compatible_handle_types
                    .contains(export_handle_types)
                {
                    return Err(SemaphoreError::ExportHandleTypesNotCompatible);
                }
            }
        }

        Ok(())
    }

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

        let mut create_info_vk = ash::vk::SemaphoreCreateInfo {
            flags: ash::vk::SemaphoreCreateFlags::empty(),
            ..Default::default()
        };
        let mut export_semaphore_create_info_vk = None;

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

        if let Some(info) = export_semaphore_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_semaphore)(
                device.handle(),
                &create_info_vk,
                ptr::null(),
                output.as_mut_ptr(),
            )
            .result()
            .map_err(VulkanError::from)?;
            output.assume_init()
        };

        Ok(Semaphore {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
            export_handle_types,
            state: Mutex::new(Default::default()),
        })
    }

    /// Takes a semaphore from the vulkano-provided semaphore pool.
    /// If the pool is empty, a new semaphore will be allocated.
    /// Upon `drop`, the semaphore is put back into the pool.
    ///
    /// For most applications, using the pool should be preferred,
    /// in order to avoid creating new semaphores every frame.
    #[inline]
    pub fn from_pool(device: Arc<Device>) -> Result<Semaphore, SemaphoreError> {
        let handle = device.semaphore_pool().lock().pop();
        let semaphore = match handle {
            Some(handle) => Semaphore {
                handle,
                device,
                id: Self::next_id(),
                must_put_in_pool: true,
                export_handle_types: ExternalSemaphoreHandleTypes::empty(),
                state: Mutex::new(Default::default()),
            },
            None => {
                // Pool is empty, alloc new semaphore
                let mut semaphore = Semaphore::new(device, Default::default())?;
                semaphore.must_put_in_pool = true;
                semaphore
            }
        };

        Ok(semaphore)
    }

    /// Creates a new `Semaphore` 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::Semaphore,
        create_info: SemaphoreCreateInfo,
    ) -> Semaphore {
        let SemaphoreCreateInfo {
            export_handle_types,
            _ne: _,
        } = create_info;

        Semaphore {
            handle,
            device,
            id: Self::next_id(),
            must_put_in_pool: false,
            export_handle_types,
            state: Mutex::new(Default::default()),
        }
    }

    /// Exports the semaphore into a POSIX file descriptor. The caller owns the returned `File`.
    #[cfg(unix)]
    #[inline]
    pub fn export_fd(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> Result<File, SemaphoreError> {
        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: ExternalSemaphoreHandleType,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self.device.enabled_extensions().khr_external_semaphore_fd {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::export_fd`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_semaphore_fd"],
                    ..Default::default()
                },
            });
        }

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

        // VUID-VkSemaphoreGetFdInfoKHR-handleType-01132
        if !self.export_handle_types.intersects(handle_type.into()) {
            return Err(SemaphoreError::HandleTypeNotEnabled);
        }

        // VUID-VkSemaphoreGetFdInfoKHR-semaphore-01133
        if let Some(imported_handle_type) = state.current_import {
            match imported_handle_type {
                ImportType::SwapchainAcquire => {
                    return Err(SemaphoreError::ImportedForSwapchainAcquire)
                }
                ImportType::ExternalSemaphore(imported_handle_type) => {
                    let external_semaphore_properties = unsafe {
                        self.device
                            .physical_device()
                            .external_semaphore_properties_unchecked(
                                ExternalSemaphoreInfo::handle_type(handle_type),
                            )
                    };

                    if !external_semaphore_properties
                        .export_from_imported_handle_types
                        .intersects(imported_handle_type.into())
                    {
                        return Err(SemaphoreError::ExportFromImportedNotSupported {
                            imported_handle_type,
                        });
                    }
                }
            }
        }

        if handle_type.has_copy_transference() {
            // VUID-VkSemaphoreGetFdInfoKHR-handleType-01134
            if state.is_wait_pending() {
                return Err(SemaphoreError::QueueIsWaiting);
            }

            // VUID-VkSemaphoreGetFdInfoKHR-handleType-01135
            // VUID-VkSemaphoreGetFdInfoKHR-handleType-03254
            if !(state.is_signaled().unwrap_or(false) || state.is_signal_pending()) {
                return Err(SemaphoreError::HandleTypeCopyNotSignaled);
            }
        }

        // VUID-VkSemaphoreGetFdInfoKHR-handleType-01136
        if !matches!(
            handle_type,
            ExternalSemaphoreHandleType::OpaqueFd | ExternalSemaphoreHandleType::SyncFd
        ) {
            return Err(SemaphoreError::HandleTypeNotFd);
        }

        Ok(())
    }

    #[cfg(unix)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn export_fd_unchecked(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> 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: ExternalSemaphoreHandleType,
        state: &mut SemaphoreState,
    ) -> Result<File, VulkanError> {
        use std::os::unix::io::FromRawFd;

        let info = ash::vk::SemaphoreGetFdInfoKHR {
            semaphore: self.handle,
            handle_type: handle_type.into(),
            ..Default::default()
        };

        let mut output = MaybeUninit::uninit();
        let fns = self.device.fns();
        (fns.khr_external_semaphore_fd.get_semaphore_fd_khr)(
            self.device.handle(),
            &info,
            output.as_mut_ptr(),
        )
        .result()
        .map_err(VulkanError::from)?;

        state.export(handle_type);

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

    /// Exports the semaphore into a Win32 handle.
    ///
    /// The [`khr_external_semaphore_win32`](crate::device::DeviceExtensions::khr_external_semaphore_win32)
    /// extension must be enabled on the device.
    #[cfg(windows)]
    #[inline]
    pub fn export_win32_handle(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> Result<*mut std::ffi::c_void, SemaphoreError> {
        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: ExternalSemaphoreHandleType,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self
            .device
            .enabled_extensions()
            .khr_external_semaphore_win32
        {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::export_win32_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_semaphore_win32"],
                    ..Default::default()
                },
            });
        }

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

        // VUID-VkSemaphoreGetWin32HandleInfoKHR-handleType-01126
        if !self.export_handle_types.intersects(handle_type.into()) {
            return Err(SemaphoreError::HandleTypeNotEnabled);
        }

        // VUID-VkSemaphoreGetWin32HandleInfoKHR-handleType-01127
        if matches!(
            handle_type,
            ExternalSemaphoreHandleType::OpaqueWin32 | ExternalSemaphoreHandleType::D3D12Fence
        ) && state.is_exported(handle_type)
        {
            return Err(SemaphoreError::AlreadyExported);
        }

        // VUID-VkSemaphoreGetWin32HandleInfoKHR-semaphore-01128
        if let Some(imported_handle_type) = state.current_import {
            match imported_handle_type {
                ImportType::SwapchainAcquire => {
                    return Err(SemaphoreError::ImportedForSwapchainAcquire)
                }
                ImportType::ExternalSemaphore(imported_handle_type) => {
                    let external_semaphore_properties = unsafe {
                        self.device
                            .physical_device()
                            .external_semaphore_properties_unchecked(
                                ExternalSemaphoreInfo::handle_type(handle_type),
                            )
                    };

                    if !external_semaphore_properties
                        .export_from_imported_handle_types
                        .intersects(imported_handle_type.into())
                    {
                        return Err(SemaphoreError::ExportFromImportedNotSupported {
                            imported_handle_type,
                        });
                    }
                }
            }
        }

        if handle_type.has_copy_transference() {
            // VUID-VkSemaphoreGetWin32HandleInfoKHR-handleType-01129
            if state.is_wait_pending() {
                return Err(SemaphoreError::QueueIsWaiting);
            }

            // VUID-VkSemaphoreGetWin32HandleInfoKHR-handleType-01130
            if !(state.is_signaled().unwrap_or(false) || state.is_signal_pending()) {
                return Err(SemaphoreError::HandleTypeCopyNotSignaled);
            }
        }

        // VUID-VkSemaphoreGetWin32HandleInfoKHR-handleType-01131
        if !matches!(
            handle_type,
            ExternalSemaphoreHandleType::OpaqueWin32
                | ExternalSemaphoreHandleType::OpaqueWin32Kmt
                | ExternalSemaphoreHandleType::D3D12Fence
        ) {
            return Err(SemaphoreError::HandleTypeNotWin32);
        }

        Ok(())
    }

    #[cfg(windows)]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn export_win32_handle_unchecked(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> 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: ExternalSemaphoreHandleType,
        state: &mut SemaphoreState,
    ) -> Result<*mut std::ffi::c_void, VulkanError> {
        let info_vk = ash::vk::SemaphoreGetWin32HandleInfoKHR {
            semaphore: self.handle,
            handle_type: handle_type.into(),
            ..Default::default()
        };

        let mut output = MaybeUninit::uninit();
        let fns = self.device.fns();
        (fns.khr_external_semaphore_win32
            .get_semaphore_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())
    }

    /// Exports the semaphore into a Zircon event handle.
    #[cfg(target_os = "fuchsia")]
    #[inline]
    pub fn export_zircon_handle(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> Result<ash::vk::zx_handle_t, SemaphoreError> {
        let mut state = self.state.lock();
        self.validate_export_zircon_handle(handle_type, &state)?;

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

    #[cfg(target_os = "fuchsia")]
    fn validate_export_zircon_handle(
        &self,
        handle_type: ExternalSemaphoreHandleType,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self.device.enabled_extensions().fuchsia_external_semaphore {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::export_zircon_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["fuchsia_external_semaphore"],
                    ..Default::default()
                },
            });
        }

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

        // VUID-VkSemaphoreGetZirconHandleInfoFUCHSIA-handleType-04758
        if !self.export_handle_types.intersects(&handle_type.into()) {
            return Err(SemaphoreError::HandleTypeNotEnabled);
        }

        // VUID-VkSemaphoreGetZirconHandleInfoFUCHSIA-semaphore-04759
        if let Some(imported_handle_type) = state.current_import {
            match imported_handle_type {
                ImportType::SwapchainAcquire => {
                    return Err(SemaphoreError::ImportedForSwapchainAcquire)
                }
                ImportType::ExternalSemaphore(imported_handle_type) => {
                    let external_semaphore_properties = unsafe {
                        self.device
                            .physical_device()
                            .external_semaphore_properties_unchecked(
                                ExternalSemaphoreInfo::handle_type(handle_type),
                            )
                    };

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

        if handle_type.has_copy_transference() {
            // VUID-VkSemaphoreGetZirconHandleInfoFUCHSIA-handleType-04760
            if state.is_wait_pending() {
                return Err(SemaphoreError::QueueIsWaiting);
            }

            // VUID-VkSemaphoreGetZirconHandleInfoFUCHSIA-handleType-04761
            if !(state.is_signaled().unwrap_or(false) || state.is_signal_pending()) {
                return Err(SemaphoreError::HandleTypeCopyNotSignaled);
            }
        }

        // VUID-VkSemaphoreGetZirconHandleInfoFUCHSIA-handleType-04762
        if !matches!(handle_type, ExternalSemaphoreHandleType::ZirconEvent) {
            return Err(SemaphoreError::HandleTypeNotZircon);
        }

        Ok(())
    }

    #[cfg(target_os = "fuchsia")]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn export_zircon_handle_unchecked(
        &self,
        handle_type: ExternalSemaphoreHandleType,
    ) -> Result<ash::vk::zx_handle_t, VulkanError> {
        let mut state = self.state.lock();
        self.export_zircon_handle_unchecked_locked(handle_type, &mut state)
    }

    #[cfg(target_os = "fuchsia")]
    unsafe fn export_zircon_handle_unchecked_locked(
        &self,
        handle_type: ExternalSemaphoreHandleType,
        state: &mut SemaphoreState,
    ) -> Result<ash::vk::zx_handle_t, VulkanError> {
        let info = ash::vk::SemaphoreGetZirconHandleInfoFUCHSIA {
            semaphore: self.handle,
            handle_type: handle_type.into(),
            ..Default::default()
        };

        let mut output = MaybeUninit::uninit();
        let fns = self.device.fns();
        (fns.fuchsia_external_semaphore
            .get_semaphore_zircon_handle_fuchsia)(
            self.device.handle(), &info, output.as_mut_ptr()
        )
        .result()
        .map_err(VulkanError::from)?;

        state.export(handle_type);

        Ok(output.assume_init())
    }

    /// Imports a semaphore from a POSIX file descriptor.
    ///
    /// The [`khr_external_semaphore_fd`](crate::device::DeviceExtensions::khr_external_semaphore_fd)
    /// extension must be enabled on the device.
    ///
    /// # Safety
    ///
    /// - If in `import_semaphore_fd_info`, `handle_type` is `ExternalHandleType::OpaqueFd`,
    ///   then `file` must represent a binary semaphore 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_semaphore_fd_info: ImportSemaphoreFdInfo,
    ) -> Result<(), SemaphoreError> {
        let mut state = self.state.lock();
        self.validate_import_fd(&import_semaphore_fd_info, &state)?;

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

    #[cfg(unix)]
    fn validate_import_fd(
        &self,
        import_semaphore_fd_info: &ImportSemaphoreFdInfo,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self.device.enabled_extensions().khr_external_semaphore_fd {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::import_fd`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_semaphore_fd"],
                    ..Default::default()
                },
            });
        }

        // VUID-vkImportSemaphoreFdKHR-semaphore-01142
        if state.is_in_queue() {
            return Err(SemaphoreError::InQueue);
        }

        let &ImportSemaphoreFdInfo {
            flags,
            handle_type,
            file: _,
            _ne: _,
        } = import_semaphore_fd_info;

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

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

        // VUID-VkImportSemaphoreFdInfoKHR-handleType-01143
        if !matches!(
            handle_type,
            ExternalSemaphoreHandleType::OpaqueFd | ExternalSemaphoreHandleType::SyncFd
        ) {
            return Err(SemaphoreError::HandleTypeNotFd);
        }

        // VUID-VkImportSemaphoreFdInfoKHR-fd-01544
        // VUID-VkImportSemaphoreFdInfoKHR-handleType-03263
        // Can't validate, therefore unsafe

        // VUID-VkImportSemaphoreFdInfoKHR-handleType-07307
        if handle_type.has_copy_transference() && !flags.intersects(SemaphoreImportFlags::TEMPORARY)
        {
            return Err(SemaphoreError::HandletypeCopyNotTemporary);
        }

        Ok(())
    }

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

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

        let ImportSemaphoreFdInfo {
            flags,
            handle_type,
            file,
            _ne: _,
        } = import_semaphore_fd_info;

        let info_vk = ash::vk::ImportSemaphoreFdInfoKHR {
            semaphore: 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_semaphore_fd.import_semaphore_fd_khr)(self.device.handle(), &info_vk)
            .result()
            .map_err(VulkanError::from)?;

        state.import(
            handle_type,
            flags.intersects(SemaphoreImportFlags::TEMPORARY),
        );

        Ok(())
    }

    /// Imports a semaphore from a Win32 handle.
    ///
    /// The [`khr_external_semaphore_win32`](crate::device::DeviceExtensions::khr_external_semaphore_win32)
    /// extension must be enabled on the device.
    ///
    /// # Safety
    ///
    /// - In `import_semaphore_win32_handle_info`, `handle` must represent a binary semaphore 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_semaphore_win32_handle_info: ImportSemaphoreWin32HandleInfo,
    ) -> Result<(), SemaphoreError> {
        let mut state = self.state.lock();
        self.validate_import_win32_handle(&import_semaphore_win32_handle_info, &state)?;

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

    #[cfg(windows)]
    fn validate_import_win32_handle(
        &self,
        import_semaphore_win32_handle_info: &ImportSemaphoreWin32HandleInfo,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self
            .device
            .enabled_extensions()
            .khr_external_semaphore_win32
        {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::import_win32_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["khr_external_semaphore_win32"],
                    ..Default::default()
                },
            });
        }

        // VUID?
        if state.is_in_queue() {
            return Err(SemaphoreError::InQueue);
        }

        let &ImportSemaphoreWin32HandleInfo {
            flags,
            handle_type,
            handle: _,
            _ne: _,
        } = import_semaphore_win32_handle_info;

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

        // VUID-VkImportSemaphoreWin32HandleInfoKHR-handleType-01140
        handle_type.validate_device(&self.device)?;

        // VUID-VkImportSemaphoreWin32HandleInfoKHR-handleType-01140
        if !matches!(
            handle_type,
            ExternalSemaphoreHandleType::OpaqueWin32
                | ExternalSemaphoreHandleType::OpaqueWin32Kmt
                | ExternalSemaphoreHandleType::D3D12Fence
        ) {
            return Err(SemaphoreError::HandleTypeNotWin32);
        }

        // VUID-VkImportSemaphoreWin32HandleInfoKHR-handle-01542
        // Can't validate, therefore unsafe

        // VUID?
        if handle_type.has_copy_transference() && !flags.intersects(SemaphoreImportFlags::TEMPORARY)
        {
            return Err(SemaphoreError::HandletypeCopyNotTemporary);
        }

        Ok(())
    }

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

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

        let info_vk = ash::vk::ImportSemaphoreWin32HandleInfoKHR {
            semaphore: 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_semaphore_win32
            .import_semaphore_win32_handle_khr)(self.device.handle(), &info_vk)
        .result()
        .map_err(VulkanError::from)?;

        state.import(
            handle_type,
            flags.intersects(SemaphoreImportFlags::TEMPORARY),
        );

        Ok(())
    }

    /// Imports a semaphore from a Zircon event handle.
    ///
    /// The [`fuchsia_external_semaphore`](crate::device::DeviceExtensions::fuchsia_external_semaphore)
    /// extension must be enabled on the device.
    ///
    /// # Safety
    ///
    /// - In `import_semaphore_zircon_handle_info`, `zircon_handle` must have `ZX_RIGHTS_BASIC` and
    ///   `ZX_RIGHTS_SIGNAL`.
    #[cfg(target_os = "fuchsia")]
    #[inline]
    pub unsafe fn import_zircon_handle(
        &self,
        import_semaphore_zircon_handle_info: ImportSemaphoreZirconHandleInfo,
    ) -> Result<(), SemaphoreError> {
        let mut state = self.state.lock();
        self.validate_import_zircon_handle(&import_semaphore_zircon_handle_info, &state)?;

        Ok(self.import_zircon_handle_unchecked_locked(
            import_semaphore_zircon_handle_info,
            &mut state,
        )?)
    }

    #[cfg(target_os = "fuchsia")]
    fn validate_import_zircon_handle(
        &self,
        import_semaphore_zircon_handle_info: &ImportSemaphoreZirconHandleInfo,
        state: &SemaphoreState,
    ) -> Result<(), SemaphoreError> {
        if !self.device.enabled_extensions().fuchsia_external_semaphore {
            return Err(SemaphoreError::RequirementNotMet {
                required_for: "`Semaphore::import_zircon_handle`",
                requires_one_of: RequiresOneOf {
                    device_extensions: &["fuchsia_external_semaphore"],
                    ..Default::default()
                },
            });
        }

        // VUID-vkImportSemaphoreZirconHandleFUCHSIA-semaphore-04764
        if state.is_in_queue() {
            return Err(SemaphoreError::InQueue);
        }

        let &ImportSemaphoreZirconHandleInfo {
            flags,
            handle_type,
            zircon_handle: _,
            _ne: _,
        } = import_semaphore_zircon_handle_info;

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

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

        // VUID-VkImportSemaphoreZirconHandleInfoFUCHSIA-handleType-04765
        if !matches!(handle_type, ExternalSemaphoreHandleType::ZirconEvent) {
            return Err(SemaphoreError::HandleTypeNotFd);
        }

        // VUID-VkImportSemaphoreZirconHandleInfoFUCHSIA-zirconHandle-04766
        // VUID-VkImportSemaphoreZirconHandleInfoFUCHSIA-zirconHandle-04767
        // Can't validate, therefore unsafe

        if handle_type.has_copy_transference() && !flags.intersects(SemaphoreImportFlags::TEMPORARY)
        {
            return Err(SemaphoreError::HandletypeCopyNotTemporary);
        }

        Ok(())
    }

    #[cfg(target_os = "fuchsia")]
    #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
    #[inline]
    pub unsafe fn import_zircon_handle_unchecked(
        &self,
        import_semaphore_zircon_handle_info: ImportSemaphoreZirconHandleInfo,
    ) -> Result<(), VulkanError> {
        let mut state = self.state.lock();
        self.import_zircon_handle_unchecked_locked(import_semaphore_zircon_handle_info, &mut state)
    }

    #[cfg(target_os = "fuchsia")]
    unsafe fn import_zircon_handle_unchecked_locked(
        &self,
        import_semaphore_zircon_handle_info: ImportSemaphoreZirconHandleInfo,
        state: &mut SemaphoreState,
    ) -> Result<(), VulkanError> {
        let ImportSemaphoreZirconHandleInfo {
            flags,
            handle_type,
            zircon_handle,
            _ne: _,
        } = import_semaphore_zircon_handle_info;

        let info_vk = ash::vk::ImportSemaphoreZirconHandleInfoFUCHSIA {
            semaphore: self.handle,
            flags: flags.into(),
            handle_type: handle_type.into(),
            zircon_handle,
            ..Default::default()
        };

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

        state.import(
            handle_type,
            flags.intersects(SemaphoreImportFlags::TEMPORARY),
        );

        Ok(())
    }

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

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

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

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

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

impl_id_counter!(Semaphore);

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

    reference_exported: bool,
    exported_handle_types: ExternalSemaphoreHandleTypes,
    current_import: Option<ImportType>,
    permanent_import: Option<ExternalSemaphoreHandleType>,
}

impl SemaphoreState {
    /// If the semaphore does not have a pending operation and has no external references,
    /// returns the current status.
    #[inline]
    fn is_signaled(&self) -> Option<bool> {
        // If any of these is true, we can't be certain of the status.
        if self.pending_signal.is_some()
            || self.pending_wait.is_some()
            || self.has_external_reference()
        {
            None
        } else {
            Some(self.is_signaled)
        }
    }

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

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

    #[inline]
    fn is_in_queue(&self) -> bool {
        matches!(self.pending_signal, Some(SignalType::Queue(_))) || self.pending_wait.is_some()
    }

    /// Returns whether there are any potential external references to the semaphore payload.
    /// That is, the semaphore 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: ExternalSemaphoreHandleType) -> 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(SignalType::Queue(Arc::downgrade(queue)));
    }

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

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

    /// Called when a queue is unlocking resources.
    #[inline]
    pub(crate) unsafe fn set_wait_finished(&mut self) {
        self.pending_wait = None;
        self.current_import = self.permanent_import.map(Into::into);
        self.is_signaled = false;
    }

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

        if handle_type.has_copy_transference() {
            self.current_import = self.permanent_import.map(Into::into);
            self.is_signaled = false;
        } else {
            self.reference_exported = true;
        }
    }

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

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

    #[inline]
    pub(crate) unsafe fn swapchain_acquire(&mut self) {
        self.pending_signal = Some(SignalType::SwapchainAcquire);
        self.current_import = Some(ImportType::SwapchainAcquire);
    }
}

#[derive(Clone, Debug)]
enum SignalType {
    Queue(Weak<Queue>),
    SwapchainAcquire,
}

#[derive(Clone, Copy, Debug)]
enum ImportType {
    SwapchainAcquire,
    ExternalSemaphore(ExternalSemaphoreHandleType),
}

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

/// Parameters to create a new `Semaphore`.
#[derive(Clone, Debug)]
pub struct SemaphoreCreateInfo {
    /// The handle types that can be exported from the semaphore.
    ///
    /// The default value is [`ExternalSemaphoreHandleTypes::empty()`].
    pub export_handle_types: ExternalSemaphoreHandleTypes,

    pub _ne: crate::NonExhaustive,
}

impl Default for SemaphoreCreateInfo {
    #[inline]
    fn default() -> Self {
        Self {
            export_handle_types: ExternalSemaphoreHandleTypes::empty(),
            _ne: crate::NonExhaustive(()),
        }
    }
}

vulkan_bitflags_enum! {
    #[non_exhaustive]

    /// A set of [`ExternalSemaphoreHandleType`] values.
    ExternalSemaphoreHandleTypes,

    /// The handle type used to export or import semaphores to/from an external source.
    ExternalSemaphoreHandleType impl {
        /// 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 no queue is waiting on the semaphore, and only if the semaphore
        /// is already signaled, or if there is a semaphore 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-semaphore-handletypes-win32
            // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-semaphore-handletypes-fd
            // https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap7.html#synchronization-semaphore-handletypes-fuchsia
            matches!(self, Self::SyncFd)
        }
    },

    = ExternalSemaphoreHandleTypeFlags(u32);

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

    /// A Windows NT handle that is only usable with Vulkan and compatible APIs.
    ///
    /// This handle type has *reference transference*.
    OPAQUE_WIN32, OpaqueWin32 = 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, OpaqueWin32Kmt = OPAQUE_WIN32_KMT,

    /// A Windows NT handle that refers to a Direct3D 11 or 12 fence.
    ///
    /// This handle type has *reference transference*.
    D3D12_FENCE, D3D12Fence = D3D12_FENCE,

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

    /// A handle to a Zircon event object.
    ///
    /// This handle type has *reference transference*.
    ///
    /// The [`fuchsia_external_semaphore`] extension must be enabled on the device.
    ///
    /// [`fuchsia_external_semaphore`]: crate::device::DeviceExtensions::fuchsia_external_semaphore
    ZIRCON_EVENT, ZirconEvent = ZIRCON_EVENT_FUCHSIA {
        device_extensions: [fuchsia_external_semaphore],
    },
}

vulkan_bitflags! {
    #[non_exhaustive]

    /// Additional parameters for a semaphore payload import.
    SemaphoreImportFlags = SemaphoreImportFlags(u32);

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

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

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

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

    pub _ne: crate::NonExhaustive,
}

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

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

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

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

    pub _ne: crate::NonExhaustive,
}

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

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

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

    /// The handle to import the semaphore from.
    ///
    /// The default value is `ZX_HANDLE_INVALID`, which must be overridden.
    pub zircon_handle: ash::vk::zx_handle_t,

    pub _ne: crate::NonExhaustive,
}

#[cfg(target_os = "fuchsia")]
impl ImportSemaphoreZirconHandleInfo {
    /// Returns an `ImportSemaphoreZirconHandleInfo` with the specified `handle_type`.
    #[inline]
    pub fn handle_type(handle_type: ExternalSemaphoreHandleType) -> Self {
        Self {
            flags: SemaphoreImportFlags::empty(),
            handle_type,
            zircon_handle: 0,
            _ne: crate::NonExhaustive(()),
        }
    }
}

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

    pub _ne: crate::NonExhaustive,
}

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

/// The properties for exporting or importing external handles, when a semaphore is created
/// with a specific configuration.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct ExternalSemaphoreProperties {
    /// 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: ExternalSemaphoreHandleTypes,

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

/// Error that can be returned from operations on a semaphore.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SemaphoreError {
    /// Not enough memory available.
    OomError(OomError),

    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: ExternalSemaphoreHandleType,
    },

    /// 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 semaphore 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
    /// semaphore.
    HandleTypeNotEnabled,

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

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

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

    /// The provided handle type is not a Zircon event handle.
    HandleTypeNotZircon,

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

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

    /// A queue is currently waiting on the semaphore.
    QueueIsWaiting,
}

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

impl Display for SemaphoreError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "not enough memory available"),
            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 semaphore 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 semaphore",
            ),
            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::HandleTypeNotZircon => {
                write!(f, "the provided handle type is not a Zircon event handle")
            }
            Self::ImportedForSwapchainAcquire => write!(
                f,
                "the semaphore currently has a temporary import for a swapchain acquire operation",
            ),
            Self::InQueue => write!(f, "the semaphore is currently in use by a queue"),
            Self::QueueIsWaiting => write!(f, "a queue is currently waiting on the semaphore"),
        }
    }
}

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

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

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

#[cfg(test)]
mod tests {
    #[cfg(unix)]
    use crate::{
        device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo},
        instance::{Instance, InstanceCreateInfo, InstanceExtensions},
        sync::semaphore::{
            ExternalSemaphoreHandleType, ExternalSemaphoreHandleTypes, SemaphoreCreateInfo,
        },
        VulkanLibrary,
    };
    use crate::{sync::semaphore::Semaphore, VulkanObject};

    #[test]
    fn semaphore_create() {
        let (device, _) = gfx_dev_and_queue!();
        let _ = Semaphore::new(device, Default::default());
    }

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

        assert_eq!(device.semaphore_pool().lock().len(), 0);
        let sem1_internal_obj = {
            let sem = Semaphore::from_pool(device.clone()).unwrap();
            assert_eq!(device.semaphore_pool().lock().len(), 0);
            sem.handle()
        };

        assert_eq!(device.semaphore_pool().lock().len(), 1);
        let sem2 = Semaphore::from_pool(device.clone()).unwrap();
        assert_eq!(device.semaphore_pool().lock().len(), 0);
        assert_eq!(sem2.handle(), sem1_internal_obj);
    }

    #[test]
    #[cfg(unix)]
    fn semaphore_export_fd() {
        let library = match VulkanLibrary::new() {
            Ok(x) => x,
            Err(_) => return,
        };

        let instance = match Instance::new(
            library,
            InstanceCreateInfo {
                enabled_extensions: InstanceExtensions {
                    khr_get_physical_device_properties2: true,
                    khr_external_semaphore_capabilities: true,
                    ..InstanceExtensions::empty()
                },
                ..Default::default()
            },
        ) {
            Ok(x) => x,
            Err(_) => return,
        };

        let physical_device = match instance.enumerate_physical_devices() {
            Ok(mut x) => x.next().unwrap(),
            Err(_) => return,
        };

        let (device, _) = match Device::new(
            physical_device,
            DeviceCreateInfo {
                enabled_extensions: DeviceExtensions {
                    khr_external_semaphore: true,
                    khr_external_semaphore_fd: true,
                    ..DeviceExtensions::empty()
                },
                queue_create_infos: vec![QueueCreateInfo {
                    queue_family_index: 0,
                    ..Default::default()
                }],
                ..Default::default()
            },
        ) {
            Ok(x) => x,
            Err(_) => return,
        };

        let sem = Semaphore::new(
            device,
            SemaphoreCreateInfo {
                export_handle_types: ExternalSemaphoreHandleTypes::OPAQUE_FD,
                ..Default::default()
            },
        )
        .unwrap();
        let _fd = sem
            .export_fd(ExternalSemaphoreHandleType::OpaqueFd)
            .unwrap();
    }
}
