Import image from dma_buf following VK_EXT_external_memory_dma_buf (#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.
This commit is contained in:
DavidR86 2023-04-01 11:08:30 +02:00 committed by GitHub
parent 836b9098ea
commit b7ecee345d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 223 additions and 4 deletions

View File

@ -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,
@ -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(())
}
@ -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,

View File

@ -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},
@ -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.

View File

@ -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},
@ -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(
@ -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> {

View File

@ -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::{
@ -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();
@ -206,6 +208,14 @@ 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.
@ -213,9 +223,13 @@ 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
}
};
// 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);
@ -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());
@ -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();
@ -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());
@ -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
}
};
@ -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);
}
@ -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
@ -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,
}
@ -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(()),
}
}
@ -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 {
@ -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`"),
}
}
}

View File

@ -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()

View File

@ -784,6 +784,7 @@ impl From<ImageTiling> for AllocationType {
match tiling {
ImageTiling::Optimal => AllocationType::NonLinear,
ImageTiling::Linear => AllocationType::Linear,
ImageTiling::DrmFormatModifier => AllocationType::Linear, // TODO: improve
}
}
}