Skip to content

Commit

Permalink
Import image from dma_buf following VK_EXT_external_memory_dma_buf (v…
Browse files Browse the repository at this point in the history
…ulkano-rs#2145)

* Import image from dma_buf

Implements importing an image into Vulkan from a Linux dma_buf,
according to the following Vulkan extensions:
- https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_external_memory_dma_buf.html
- https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_EXT_image_drm_format_modifier.html

* Only compile dmabuf image importing on Linux

Adds conditional compilation checks to functionality for importing
vulkan images from a Linux dmabuf, as doing this only makes sense on Linux.

* Add VUID checking for VkImageCreateInfo

* Avoid Linux-only dependencies on other OSs

* Add missing initializer to StorageImage

* Add more VUID validation

Check for
VUID-vkGetPhysicalDeviceImageFormatProperties-tiling-02248, and VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02249

* Add some more VUIDs

Or explanations of why they cannot yet be added

* Small fix

* Add suggested fixes

Use lowercase for error, replace panic! with todo!, and make some
comments show up in documentation.
  • Loading branch information
spaghetti-stack authored and hakolao committed Feb 20, 2024
1 parent b1b70ca commit 88277d2
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 4 deletions.
11 changes: 10 additions & 1 deletion vulkano/src/device/physical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
},
instance::Instance,
macros::{impl_id_counter, vulkan_bitflags, vulkan_enum},
memory::MemoryProperties,
memory::{ExternalMemoryHandleType, MemoryProperties},
swapchain::{
ColorSpace, FullScreenExclusive, PresentMode, Surface, SurfaceApi, SurfaceCapabilities,
SurfaceInfo, SurfaceTransforms,
Expand Down Expand Up @@ -1023,6 +1023,9 @@ impl PhysicalDevice {
image_view_type.validate_physical_device(self)?;
}

// TODO: VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02313
// Currently there is nothing in Vulkano for for adding a VkImageFormatListCreateInfo.

Ok(())
}

Expand Down Expand Up @@ -1158,6 +1161,12 @@ impl PhysicalDevice {
if !info2_vk.p_next.is_null() {
return Ok(None);
}
if let Some(ExternalMemoryHandleType::DmaBuf) = external_memory_handle_type
{
// VUID-vkGetPhysicalDeviceImageFormatProperties-tiling-02248
// VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02249
return Ok(None);
}

(fns.v1_0.get_physical_device_image_format_properties)(
self.handle,
Expand Down
7 changes: 5 additions & 2 deletions vulkano/src/image/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ pub use self::{
usage::ImageUsage,
view::{ImageViewAbstract, ImageViewType},
};

#[cfg(target_os = "linux")]
pub use self::storage::SubresourceData;

use crate::{
format::Format,
macros::{vulkan_bitflags, vulkan_bitflags_enum, vulkan_enum},
Expand Down Expand Up @@ -367,11 +371,10 @@ vulkan_enum! {
// TODO: document
Linear = LINEAR,

/* TODO: enable
// TODO: document
DrmFormatModifier = DRM_FORMAT_MODIFIER_EXT {
device_extensions: [ext_image_drm_format_modifier],
},*/
},
}

/// The dimensions of an image.
Expand Down
165 changes: 165 additions & 0 deletions vulkano/src/image/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ use crate::{
DeviceSize,
};
use smallvec::SmallVec;

#[cfg(target_os = "linux")]
use crate::{
image::ImageTiling,
memory::{allocator::MemoryAlloc, DeviceMemory, MemoryAllocateFlags, MemoryAllocateInfo},
};
#[cfg(target_os = "linux")]
use ash::vk::{ImageDrmFormatModifierExplicitCreateInfoEXT, SubresourceLayout};
#[cfg(target_os = "linux")]
use std::os::unix::prelude::{FromRawFd, IntoRawFd, RawFd};

use std::{
fs::File,
hash::{Hash, Hasher},
Expand Down Expand Up @@ -223,6 +234,146 @@ impl StorageImage {
}
}

#[cfg(target_os = "linux")]
/// Creates a new image from a set of Linux dma_buf file descriptors. The memory will be imported from the file desciptors, and will be bound to the image.
/// # Arguments
/// * `fds` - The list of file descriptors to import from. Single planar images should only use one, and multiplanar images can use multiple, for example, for each color.
/// * `offset` - The byte offset from the start of the image of the plane where the image subresource begins.
/// * `pitch` - Describes the number of bytes between each row of texels in an image.
pub fn new_from_dma_buf_fd(
allocator: &(impl MemoryAllocator + ?Sized),
device: Arc<Device>,
dimensions: ImageDimensions,
format: Format,
usage: ImageUsage,
flags: ImageCreateFlags,
queue_family_indices: impl IntoIterator<Item = u32>,
mut subresource_data: Vec<SubresourceData>,
drm_format_modifier: u64,
) -> Result<Arc<StorageImage>, ImageError> {
let queue_family_indices: SmallVec<[_; 4]> = queue_family_indices.into_iter().collect();

// TODO: Support multiplanar image importing from Linux FD
if subresource_data.len() > 1 {
todo!();
}

// Create a vector of the layout of each image plane.

// All of the following are automatically true, since the values are explicitly set as such:
// VUID-VkImageDrmFormatModifierExplicitCreateInfoEXT-size-02267
// VUID-VkImageDrmFormatModifierExplicitCreateInfoEXT-arrayPitch-02268
// VUID-VkImageDrmFormatModifierExplicitCreateInfoEXT-depthPitch-02269
let layout: Vec<SubresourceLayout> = subresource_data
.iter_mut()
.map(
|SubresourceData {
fd: _,
offset,
row_pitch,
}| {
SubresourceLayout {
offset: *offset,
size: 0,
row_pitch: *row_pitch,
array_pitch: 0_u64,
depth_pitch: 0_u64,
}
},
)
.collect();

let fds: Vec<RawFd> = subresource_data
.iter_mut()
.map(
|SubresourceData {
fd,
offset: _,
row_pitch: _,
}| { *fd },
)
.collect();

let drm_mod = ImageDrmFormatModifierExplicitCreateInfoEXT::builder()
.drm_format_modifier(drm_format_modifier)
.plane_layouts(layout.as_ref())
.build();

let external_memory_handle_types = ExternalMemoryHandleTypes::DMA_BUF;

let image = RawImage::new(
device.clone(),
ImageCreateInfo {
flags,
dimensions,
format: Some(format),
usage,
sharing: if queue_family_indices.len() >= 2 {
Sharing::Concurrent(queue_family_indices)
} else {
Sharing::Exclusive
},
external_memory_handle_types,
tiling: ImageTiling::DrmFormatModifier,
image_drm_format_modifier_create_info: Some(drm_mod),
..Default::default()
},
)?;

let requirements = image.memory_requirements()[0];
let memory_type_index = allocator
.find_memory_type_index(requirements.memory_type_bits, MemoryUsage::GpuOnly.into())
.expect("failed to find a suitable memory type");

assert!(device.enabled_extensions().khr_external_memory_fd);
assert!(device.enabled_extensions().khr_external_memory);
assert!(device.enabled_extensions().ext_external_memory_dma_buf);

let memory = unsafe {
// TODO: For completeness, importing memory from muliple file descriptors should be added (In order to support importing multiplanar images). As of now, only single planar image importing will work.
if fds.len() != 1 {
todo!();
}

// Try cloning underlying fd
let file = File::from_raw_fd(*fds.first().expect("file descriptor Vec is empty"));
let new_file = file.try_clone().expect("error cloning file descriptor");

// Turn the original file descriptor back into a raw fd to avoid ownership problems
file.into_raw_fd();
DeviceMemory::import(
device,
MemoryAllocateInfo {
allocation_size: requirements.layout.size(),
memory_type_index,
dedicated_allocation: Some(DedicatedAllocation::Image(&image)),
export_handle_types: ExternalMemoryHandleTypes::empty(),
flags: MemoryAllocateFlags::empty(),
..Default::default()
},
crate::memory::MemoryImportInfo::Fd {
handle_type: crate::memory::ExternalMemoryHandleType::DmaBuf,
file: new_file,
},
)
.unwrap() // TODO: Handle
};

let mem_alloc = MemoryAlloc::new(memory).unwrap();

debug_assert!(mem_alloc.offset() % requirements.layout.alignment().as_nonzero() == 0);
debug_assert!(mem_alloc.size() == requirements.layout.size());

let inner = Arc::new(unsafe {
image
.bind_memory_unchecked([mem_alloc])
.map_err(|(err, _, _)| err)?
});
Ok(Arc::new(StorageImage {
inner,
layout_initialized: AtomicBool::new(false),
}))
}
/// Allows the creation of a simple 2D general purpose image view from `StorageImage`.
#[inline]
pub fn general_purpose_image_view(
Expand Down Expand Up @@ -285,6 +436,20 @@ impl StorageImage {
}
}

#[cfg(target_os = "linux")]
/// Struct that contains a Linux file descriptor for importing, when creating an image. Since a file descriptor is used for each
/// plane in the case of multiplanar images, each fd needs to have an offset and a row pitch in order to interpret the imported data.
pub struct SubresourceData {
/// The file descriptor handle of a layer of an image.
pub fd: RawFd,

/// The byte offset from the start of the plane where the image subresource begins.
pub offset: u64,

/// Describes the number of bytes between each row of texels in an image plane.
pub row_pitch: u64,
}

unsafe impl DeviceOwned for StorageImage {
#[inline]
fn device(&self) -> &Arc<Device> {
Expand Down
42 changes: 41 additions & 1 deletion vulkano/src/image/sys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ use crate::{
sync::{future::AccessError, CurrentAccess, Sharing},
DeviceSize, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
};
use ash::vk::ImageDrmFormatModifierExplicitCreateInfoEXT;
use parking_lot::{Mutex, MutexGuard};
use smallvec::{smallvec, SmallVec};
use std::{
Expand Down Expand Up @@ -134,6 +135,7 @@ impl RawImage {
initial_layout,
external_memory_handle_types,
_ne: _,
image_drm_format_modifier_create_info,
} = create_info;

let physical_device = device.physical_device();
Expand Down Expand Up @@ -206,16 +208,28 @@ impl RawImage {
|| flags.intersects(ImageCreateFlags::MUTABLE_FORMAT)
);

// VUID-VkImageCreateInfo-tiling-02261
// VUID-VkImageCreateInfo-pNext-02262
if (tiling == ImageTiling::DrmFormatModifier)
!= image_drm_format_modifier_create_info.is_some()
{
return Err(ImageError::DrmFormatModifierRequiresCreateInfo);
}

// Get format features
let format_features = {
// Use unchecked, because all validation has been done above.
let format_properties = unsafe { physical_device.format_properties_unchecked(format) };
match tiling {
ImageTiling::Linear => format_properties.linear_tiling_features,
ImageTiling::Optimal => format_properties.optimal_tiling_features,
ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: Improve
}
};

// TODO: VUID-VkImageCreateInfo-tiling-02353
// Vulkano currently has no high-level way to add or check for VkImageFormatListCreateInfo.

// Format isn't supported at all?
if format_features.is_empty() {
return Err(ImageError::FormatNotSupported);
Expand Down Expand Up @@ -871,6 +885,7 @@ impl RawImage {
initial_layout,
external_memory_handle_types,
_ne: _,
mut image_drm_format_modifier_create_info,
} = &create_info;

let aspects = format.map_or_else(Default::default, |format| format.aspects());
Expand Down Expand Up @@ -953,6 +968,13 @@ impl RawImage {
info_vk.p_next = next as *const _ as *const _;
}

if external_memory_handle_types.intersects(ExternalMemoryHandleTypes::DMA_BUF) {
let next = image_drm_format_modifier_create_info.as_mut().unwrap();

next.p_next = info_vk.p_next;
info_vk.p_next = next as *const _ as *const _;
}

let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
Expand Down Expand Up @@ -1000,6 +1022,7 @@ impl RawImage {
initial_layout,
external_memory_handle_types,
_ne: _,
image_drm_format_modifier_create_info: _,
} = create_info;

let aspects = format.map_or_else(Default::default, |format| format.aspects());
Expand All @@ -1020,6 +1043,7 @@ impl RawImage {
match tiling {
ImageTiling::Linear => format_properties.linear_tiling_features,
ImageTiling::Optimal => format_properties.optimal_tiling_features,
ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features, // TODO: improve
}
};

Expand Down Expand Up @@ -1755,7 +1779,10 @@ impl RawImage {
// Ensured by use of enum `ImageAspect`.

// VUID-vkGetImageSubresourceLayout-image-02270
if !matches!(self.tiling, ImageTiling::Linear) {
if !matches!(
self.tiling,
ImageTiling::DrmFormatModifier | ImageTiling::Linear
) {
return Err(ImageError::OptimalTilingNotSupported);
}

Expand Down Expand Up @@ -1792,6 +1819,11 @@ impl RawImage {
allowed_aspects -= ImageAspects::COLOR;
}

// TODO: VUID-vkGetImageSubresourceLayout-tiling-02271
//if self.tiling == ImageTiling::DrmFormatModifier {
// Only one-plane image importing is possible for now.
//}

// VUID-vkGetImageSubresourceLayout-format-04461
// VUID-vkGetImageSubresourceLayout-format-04462
// VUID-vkGetImageSubresourceLayout-format-04463
Expand Down Expand Up @@ -1962,6 +1994,9 @@ pub struct ImageCreateInfo {
/// The default value is [`ExternalMemoryHandleTypes::empty()`].
pub external_memory_handle_types: ExternalMemoryHandleTypes,

/// Specify that an image be created with the provided DRM format modifier and explicit memory layout
pub image_drm_format_modifier_create_info: Option<ImageDrmFormatModifierExplicitCreateInfoEXT>,

pub _ne: crate::NonExhaustive,
}

Expand All @@ -1984,6 +2019,7 @@ impl Default for ImageCreateInfo {
sharing: Sharing::Exclusive,
initial_layout: ImageLayout::Undefined,
external_memory_handle_types: ExternalMemoryHandleTypes::empty(),
image_drm_format_modifier_create_info: None,
_ne: crate::NonExhaustive(()),
}
}
Expand Down Expand Up @@ -2960,6 +2996,9 @@ pub enum ImageError {
YcbcrFormatNot2d,

DirectImageViewCreationFailed(ImageViewCreationError),

/// If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must not be `None`.
DrmFormatModifierRequiresCreateInfo,
}

impl Error for ImageError {
Expand Down Expand Up @@ -3219,6 +3258,7 @@ impl Display for ImageError {
write!(f, "a YCbCr format was given, but the image type was not 2D")
}
Self::DirectImageViewCreationFailed(e) => write!(f, "Image view creation failed {}", e),
Self::DrmFormatModifierRequiresCreateInfo => write!(f, "If and only if tiling is `DRMFormatModifier`, then `image_drm_format_modifier_create_info` must be `Some`"),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions vulkano/src/image/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ where
match image.tiling() {
ImageTiling::Optimal => format_properties.optimal_tiling_features,
ImageTiling::Linear => format_properties.linear_tiling_features,
ImageTiling::DrmFormatModifier => format_properties.linear_tiling_features,
}
} else {
image.format_features()
Expand Down
Loading

0 comments on commit 88277d2

Please sign in to comment.