Add support for semaphore file descriptor export (#1606)

* Add external semaphore device extensions & function

* Add semaphore export functionality

* Fix test failing due to missing extensions

* Add semaphore module

* Add builder pattern for semaphore

* Add check for supported extensions before running test

* Add missing file

* Fix use with super instead of crate

* Fix pool semaphore built incorrectly; Fix semaphore types export

* Add pool parameter as semaphore builder function

* Fix test not checking instance extensions
This commit is contained in:
Francisco Ayala Le Brun 2021-06-28 09:30:06 +02:00 committed by GitHub
parent e3e210f968
commit 698d457235
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 485 additions and 161 deletions

View File

@ -36,6 +36,7 @@ use crate::swapchain::PresentRegion;
use crate::swapchain::Surface;
use crate::swapchain::SurfaceSwapchainLock;
use crate::swapchain::SurfaceTransform;
use crate::sync::semaphore::SemaphoreError;
use crate::sync::AccessCheckError;
use crate::sync::AccessError;
use crate::sync::AccessFlags;
@ -1369,6 +1370,9 @@ pub enum AcquireError {
/// The surface has changed in a way that makes the swapchain unusable. You must query the
/// surface's new properties and recreate a new swapchain if you want to continue drawing.
OutOfDate,
/// Error during semaphore creation
SemaphoreError(SemaphoreError),
}
impl error::Error for AcquireError {
@ -1396,11 +1400,18 @@ impl fmt::Display for AcquireError {
AcquireError::FullscreenExclusiveLost => {
"the swapchain no longer has fullscreen exclusivity"
}
AcquireError::SemaphoreError(_) => "error creating semaphore",
}
)
}
}
impl From<SemaphoreError> for AcquireError {
fn from(err: SemaphoreError) -> Self {
AcquireError::SemaphoreError(err)
}
}
impl From<OomError> for AcquireError {
#[inline]
fn from(err: OomError) -> AcquireError {

View File

@ -122,13 +122,15 @@ pub use self::pipeline::AccessFlags;
pub use self::pipeline::PipelineMemoryAccess;
pub use self::pipeline::PipelineStage;
pub use self::pipeline::PipelineStages;
pub use self::semaphore::ExternalSemaphoreHandleType;
pub use self::semaphore::Semaphore;
pub use self::semaphore::SemaphoreError;
mod event;
mod fence;
mod future;
mod pipeline;
mod semaphore;
pub(crate) mod semaphore;
/// Declares in which queue(s) a resource can be used.
///

View File

@ -1,160 +0,0 @@
// 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::check_errors;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::OomError;
use crate::SafeDeref;
use crate::VulkanObject;
use std::mem::MaybeUninit;
use std::ptr;
use std::sync::Arc;
/// 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<D = Arc<Device>>
where
D: SafeDeref<Target = Device>,
{
semaphore: ash::vk::Semaphore,
device: D,
must_put_in_pool: bool,
}
impl<D> Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
/// 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.
pub fn from_pool(device: D) -> Result<Semaphore<D>, OomError> {
let maybe_raw_sem = device.semaphore_pool().lock().unwrap().pop();
match maybe_raw_sem {
Some(raw_sem) => Ok(Semaphore {
device: device,
semaphore: raw_sem,
must_put_in_pool: true,
}),
None => {
// Pool is empty, alloc new semaphore
Semaphore::alloc_impl(device, true)
}
}
}
/// Builds a new semaphore.
#[inline]
pub fn alloc(device: D) -> Result<Semaphore<D>, OomError> {
Semaphore::alloc_impl(device, false)
}
fn alloc_impl(device: D, must_put_in_pool: bool) -> Result<Semaphore<D>, OomError> {
let semaphore = unsafe {
// since the creation is constant, we use a `static` instead of a struct on the stack
let infos = ash::vk::SemaphoreCreateInfo {
flags: ash::vk::SemaphoreCreateFlags::empty(),
..Default::default()
};
let fns = device.fns();
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.create_semaphore(
device.internal_object(),
&infos,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(Semaphore {
device: device,
semaphore: semaphore,
must_put_in_pool: must_put_in_pool,
})
}
}
unsafe impl DeviceOwned for Semaphore {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
unsafe impl<D> VulkanObject for Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
type Object = ash::vk::Semaphore;
#[inline]
fn internal_object(&self) -> ash::vk::Semaphore {
self.semaphore
}
}
impl<D> Drop for Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
#[inline]
fn drop(&mut self) {
unsafe {
if self.must_put_in_pool {
let raw_sem = self.semaphore;
self.device.semaphore_pool().lock().unwrap().push(raw_sem);
} else {
let fns = self.device.fns();
fns.v1_0.destroy_semaphore(
self.device.internal_object(),
self.semaphore,
ptr::null(),
);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::sync::Semaphore;
use crate::VulkanObject;
#[test]
fn semaphore_create() {
let (device, _) = gfx_dev_and_queue!();
let _ = Semaphore::alloc(device.clone());
}
#[test]
fn semaphore_pool() {
let (device, _) = gfx_dev_and_queue!();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
let sem1_internal_obj = {
let sem = Semaphore::from_pool(device.clone()).unwrap();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
sem.internal_object()
};
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 1);
let sem2 = Semaphore::from_pool(device.clone()).unwrap();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
assert_eq!(sem2.internal_object(), sem1_internal_obj);
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2021 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 std::ops::BitOr;
/// Describes the handle type used for Vulkan external semaphore APIs. This is **not**
/// just a suggestion. Check out VkExternalSemaphoreHandleTypeFlagBits in the Vulkan
/// spec.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ExternalSemaphoreHandleType {
pub opaque_fd: bool,
pub opaque_win32: bool,
pub opaque_win32_kmt: bool,
pub d3d12_fence: bool,
pub sync_fd: bool,
}
impl ExternalSemaphoreHandleType {
/// Builds a `ExternalSemaphoreHandleType` with all values set to false. Useful as a default value.
///
/// # Example
///
/// ```rust
/// use vulkano::sync::ExternalSemaphoreHandleType as ExternalSemaphoreHandleType;
///
/// let _handle_type = ExternalSemaphoreHandleType {
/// opaque_fd: true,
/// .. ExternalSemaphoreHandleType::none()
/// };
/// ```
#[inline]
pub fn none() -> ExternalSemaphoreHandleType {
ExternalSemaphoreHandleType {
opaque_fd: false,
opaque_win32: false,
opaque_win32_kmt: false,
d3d12_fence: false,
sync_fd: false,
}
}
/// Builds an `ExternalSemaphoreHandleType` for a posix file descriptor.
///
/// # Example
///
/// ```rust
/// use vulkano::sync::ExternalSemaphoreHandleType as ExternalSemaphoreHandleType;
///
/// let _handle_type = ExternalSemaphoreHandleType::posix();
/// ```
#[inline]
pub fn posix() -> ExternalSemaphoreHandleType {
ExternalSemaphoreHandleType {
opaque_fd: true,
..ExternalSemaphoreHandleType::none()
}
}
}
impl From<ExternalSemaphoreHandleType> for ash::vk::ExternalSemaphoreHandleTypeFlags {
#[inline]
fn from(val: ExternalSemaphoreHandleType) -> Self {
let mut result = ash::vk::ExternalSemaphoreHandleTypeFlags::empty();
if val.opaque_fd {
result |= ash::vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_FD;
}
if val.opaque_win32 {
result |= ash::vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32;
}
if val.opaque_win32_kmt {
result |= ash::vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32_KMT;
}
if val.d3d12_fence {
result |= ash::vk::ExternalSemaphoreHandleTypeFlags::D3D12_FENCE;
}
if val.sync_fd {
result |= ash::vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD;
}
result
}
}
impl BitOr for ExternalSemaphoreHandleType {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
ExternalSemaphoreHandleType {
opaque_fd: self.opaque_fd || rhs.opaque_fd,
opaque_win32: self.opaque_win32 || rhs.opaque_win32,
opaque_win32_kmt: self.opaque_win32_kmt || rhs.opaque_win32_kmt,
d3d12_fence: self.d3d12_fence || rhs.d3d12_fence,
sync_fd: self.sync_fd || rhs.sync_fd,
}
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2021 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.
pub use self::external_semaphore_handle_type::ExternalSemaphoreHandleType;
pub use self::semaphore::Semaphore;
pub use self::semaphore::SemaphoreError;
mod external_semaphore_handle_type;
mod semaphore;

View File

@ -0,0 +1,355 @@
// 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::check_errors;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::Error;
use crate::OomError;
use crate::SafeDeref;
use crate::VulkanObject;
use std::fmt;
#[cfg(target_os = "linux")]
use std::fs::File;
use std::mem::MaybeUninit;
#[cfg(target_os = "linux")]
use std::os::unix::io::FromRawFd;
use std::ptr;
use std::sync::Arc;
use crate::sync::semaphore::ExternalSemaphoreHandleType;
/// 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<D = Arc<Device>>
where
D: SafeDeref<Target = Device>,
{
semaphore: ash::vk::Semaphore,
device: D,
must_put_in_pool: bool,
}
// TODO: Add support for VkExportSemaphoreWin32HandleInfoKHR
// TODO: Add suport for importable semaphores
pub struct SemaphoreBuilder<D = Arc<Device>>
where
D: SafeDeref<Target = Device>,
{
device: D,
export_info: Option<ash::vk::ExportSemaphoreCreateInfo>,
create: ash::vk::SemaphoreCreateInfo,
must_put_in_pool: bool,
}
impl<D> SemaphoreBuilder<D>
where
D: SafeDeref<Target = Device>,
{
pub fn new(device: D) -> Self {
let create = ash::vk::SemaphoreCreateInfo::default();
Self {
device,
export_info: None,
create,
must_put_in_pool: false,
}
}
/// Configures the semaphore to be added to the semaphore pool once it is destroyed.
pub(crate) fn in_pool(mut self) -> Self {
self.must_put_in_pool = true;
self
}
/// Sets an optional field for exportable allocations in the `SemaphoreBuilder`.
///
/// # Panic
///
/// - Panics if the export info has already been set.
pub fn export_info(mut self, handle_types: ExternalSemaphoreHandleType) -> Self {
assert!(self.export_info.is_none());
let export_info = ash::vk::ExportSemaphoreCreateInfo {
handle_types: handle_types.into(),
..Default::default()
};
self.export_info = Some(export_info);
self.create.p_next = unsafe { std::mem::transmute(&export_info) };
self
}
pub fn build(self) -> Result<Semaphore<D>, SemaphoreError> {
if self.export_info.is_some()
&& !self
.device
.instance()
.loaded_extensions()
.khr_external_semaphore_capabilities
{
Err(SemaphoreError::MissingExtension(
"khr_external_semaphore_capabilities",
))
} else {
let semaphore = unsafe {
let fns = self.device.fns();
let mut output = MaybeUninit::uninit();
check_errors(fns.v1_0.create_semaphore(
self.device.internal_object(),
&self.create,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(Semaphore {
device: self.device,
semaphore,
must_put_in_pool: self.must_put_in_pool,
})
}
}
}
impl<D> Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
/// 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.
pub fn from_pool(device: D) -> Result<Semaphore<D>, SemaphoreError> {
let maybe_raw_sem = device.semaphore_pool().lock().unwrap().pop();
match maybe_raw_sem {
Some(raw_sem) => Ok(Semaphore {
device,
semaphore: raw_sem,
must_put_in_pool: true,
}),
None => {
// Pool is empty, alloc new semaphore
SemaphoreBuilder::new(device).in_pool().build()
}
}
}
/// Builds a new semaphore.
#[inline]
pub fn alloc(device: D) -> Result<Semaphore<D>, SemaphoreError> {
SemaphoreBuilder::new(device).build()
}
/// Same as `alloc`, but allows exportable opaque file descriptor on Linux
#[inline]
#[cfg(target_os = "linux")]
pub fn alloc_with_exportable_fd(device: D) -> Result<Semaphore<D>, SemaphoreError> {
SemaphoreBuilder::new(device)
.export_info(ExternalSemaphoreHandleType::posix())
.build()
}
#[cfg(target_os = "linux")]
pub fn export_opaque_fd(&self) -> Result<File, SemaphoreError> {
let fns = self.device.fns();
assert!(self.device.loaded_extensions().khr_external_semaphore);
assert!(self.device.loaded_extensions().khr_external_semaphore_fd);
let fd = unsafe {
let info = ash::vk::SemaphoreGetFdInfoKHR {
semaphore: self.semaphore,
handle_type: ash::vk::ExternalSemaphoreHandleTypeFlagsKHR::OPAQUE_FD,
..Default::default()
};
let mut output = MaybeUninit::uninit();
check_errors(fns.khr_external_semaphore_fd.get_semaphore_fd_khr(
self.device.internal_object(),
&info,
output.as_mut_ptr(),
))?;
output.assume_init()
};
let file = unsafe { File::from_raw_fd(fd) };
Ok(file)
}
}
unsafe impl DeviceOwned for Semaphore {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
unsafe impl<D> VulkanObject for Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
type Object = ash::vk::Semaphore;
#[inline]
fn internal_object(&self) -> ash::vk::Semaphore {
self.semaphore
}
}
impl<D> Drop for Semaphore<D>
where
D: SafeDeref<Target = Device>,
{
#[inline]
fn drop(&mut self) {
unsafe {
if self.must_put_in_pool {
let raw_sem = self.semaphore;
self.device.semaphore_pool().lock().unwrap().push(raw_sem);
} else {
let fns = self.device.fns();
fns.v1_0.destroy_semaphore(
self.device.internal_object(),
self.semaphore,
ptr::null(),
);
}
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum SemaphoreError {
/// Not enough memory available.
OomError(OomError),
/// An extensions is missing.
MissingExtension(&'static str),
}
impl fmt::Display for SemaphoreError {
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
SemaphoreError::OomError(_) => write!(fmt, "not enough memory available"),
SemaphoreError::MissingExtension(s) => {
write!(fmt, "Missing the following extension: {}", s)
}
}
}
}
impl From<Error> for SemaphoreError {
#[inline]
fn from(err: Error) -> SemaphoreError {
match err {
e @ Error::OutOfHostMemory | e @ Error::OutOfDeviceMemory => {
SemaphoreError::OomError(e.into())
}
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl std::error::Error for SemaphoreError {
#[inline]
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match *self {
SemaphoreError::OomError(ref err) => Some(err),
_ => None,
}
}
}
impl From<OomError> for SemaphoreError {
#[inline]
fn from(err: OomError) -> SemaphoreError {
SemaphoreError::OomError(err)
}
}
#[cfg(test)]
mod tests {
use crate::device::{Device, DeviceExtensions};
use crate::instance::{Instance, InstanceExtensions, PhysicalDevice};
use crate::VulkanObject;
use crate::{sync::Semaphore, Version};
#[test]
fn semaphore_create() {
let (device, _) = gfx_dev_and_queue!();
let _ = Semaphore::alloc(device.clone());
}
#[test]
fn semaphore_pool() {
let (device, _) = gfx_dev_and_queue!();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
let sem1_internal_obj = {
let sem = Semaphore::from_pool(device.clone()).unwrap();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
sem.internal_object()
};
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 1);
let sem2 = Semaphore::from_pool(device.clone()).unwrap();
assert_eq!(device.semaphore_pool().lock().unwrap().len(), 0);
assert_eq!(sem2.internal_object(), sem1_internal_obj);
}
#[test]
#[cfg(target_os = "linux")]
fn semaphore_export() {
let supported_ext = InstanceExtensions::supported_by_core().unwrap();
if supported_ext.khr_get_display_properties2
&& supported_ext.khr_external_semaphore_capabilities
{
let instance = Instance::new(
None,
Version::V1_1,
&InstanceExtensions {
khr_get_physical_device_properties2: true,
khr_external_semaphore_capabilities: true,
..InstanceExtensions::none()
},
None,
)
.unwrap();
let physical = PhysicalDevice::enumerate(&instance).next().unwrap();
let queue_family = physical.queue_families().next().unwrap();
let device_ext = DeviceExtensions {
khr_external_semaphore: true,
khr_external_semaphore_fd: true,
..DeviceExtensions::none()
};
let (device, _) = Device::new(
physical,
physical.supported_features(),
&device_ext,
[(queue_family, 0.5)].iter().cloned(),
)
.unwrap();
let supported_ext = DeviceExtensions::supported_by_device(physical.clone());
if supported_ext.khr_external_semaphore && supported_ext.khr_external_semaphore_fd {
let sem = Semaphore::alloc_with_exportable_fd(device.clone()).unwrap();
let fd = sem.export_opaque_fd().unwrap();
}
}
}
}