use crate::device::VulkanContext;
use crate::resource::{backend_err, vk_format, TextureMemory, VulkanTexture};
use ash::vk;
use impeller_hal::{
Error, Extent2D, FormatModifierSet, Modifier, PixelFormat, Result, TextureUsage,
};
pub fn query_format_modifiers(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
format: PixelFormat,
) -> Option<FormatModifierSet> {
let fourcc = format.fourcc()?;
let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
let mut properties = vk::FormatProperties2::default().push_next(&mut list);
unsafe {
instance.get_physical_device_format_properties2(
physical_device,
vk_format(format),
&mut properties,
);
}
let count = list.drm_format_modifier_count as usize;
if count == 0 {
return None;
}
let mut entries = vec![vk::DrmFormatModifierPropertiesEXT::default(); count];
list.p_drm_format_modifier_properties = entries.as_mut_ptr();
let mut properties = vk::FormatProperties2::default().push_next(&mut list);
unsafe {
instance.get_physical_device_format_properties2(
physical_device,
vk_format(format),
&mut properties,
);
}
let modifiers: Vec<Modifier> = entries
.iter()
.take(count)
.filter(|e| {
e.drm_format_modifier_tiling_features
.contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT)
})
.map(|e| Modifier(e.drm_format_modifier))
.collect();
if modifiers.is_empty() {
return None;
}
Some(FormatModifierSet::new(fourcc, modifiers))
}
impl VulkanContext {
pub fn create_exportable_texture(
&mut self,
extent: Extent2D,
format: PixelFormat,
modifiers: &[Modifier],
) -> Result<VulkanTexture> {
if !self.capabilities().dma_buf.can_allocate_scanout() {
return Err(Error::Unsupported(
"exporting images requires dma-buf export and explicit modifiers",
));
}
if modifiers.is_empty() {
return Err(Error::Unsupported("no candidate modifiers were offered"));
}
if !self.capabilities().can_allocate(extent) {
return Err(Error::LimitExceeded {
what: "texture dimension",
requested: extent.width.max(extent.height) as u64,
limit: self.capabilities().max_texture_size as u64,
});
}
let device = self.raw_device().clone();
let raw_modifiers: Vec<u64> = modifiers.iter().map(|m| m.0).collect();
let mut modifier_list = vk::ImageDrmFormatModifierListCreateInfoEXT::default()
.drm_format_modifiers(&raw_modifiers);
let mut external = vk::ExternalMemoryImageCreateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk_format(format))
.extent(vk::Extent3D {
width: extent.width,
height: extent.height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::TRANSFER_SRC)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.push_next(&mut modifier_list)
.push_next(&mut external);
let image = unsafe { device.create_image(&info, None) }
.map_err(|e| backend_err("create_image (exportable)", e))?;
let requirements = unsafe { device.get_image_memory_requirements(image) };
let memory_type = match self.find_memory_type(
requirements.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
) {
Some(index) => index,
None => {
unsafe { device.destroy_image(image, None) };
return Err(Error::Backend {
backend: "vulkan",
detail: "no device-local memory type accepts an exportable image".into(),
});
}
};
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
let mut export = vk::ExportMemoryAllocateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let allocate = vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(memory_type)
.push_next(&mut dedicated)
.push_next(&mut export);
let memory = match unsafe { device.allocate_memory(&allocate, None) } {
Ok(memory) => memory,
Err(e) => {
unsafe { device.destroy_image(image, None) };
return Err(backend_err("allocate_memory (exportable)", e));
}
};
if let Err(e) = unsafe { device.bind_image_memory(image, memory, 0) } {
unsafe {
device.free_memory(memory, None);
device.destroy_image(image, None);
}
return Err(backend_err("bind_image_memory (exportable)", e));
}
Ok(VulkanTexture {
image,
memory: TextureMemory::Dedicated(memory),
extent,
format,
layout: std::cell::Cell::new(vk::ImageLayout::UNDEFINED),
mip_levels: 1,
usage: TextureUsage {
render_target: true,
transfer: true,
scanout: true,
..TextureUsage::default()
},
})
}
pub fn texture_modifier(&self, texture: &VulkanTexture) -> Result<Modifier> {
if !texture.usage.scanout {
return Err(Error::Unsupported(
"only an exportable image has a negotiated modifier",
));
}
let loader = ash::ext::image_drm_format_modifier::Device::new(
self.raw_instance(),
self.raw_device(),
);
let mut properties = vk::ImageDrmFormatModifierPropertiesEXT::default();
unsafe {
loader
.get_image_drm_format_modifier_properties(texture.image, &mut properties)
.map_err(|e| backend_err("get_image_drm_format_modifier_properties", e))?;
}
Ok(Modifier(properties.drm_format_modifier))
}
#[cfg(unix)]
pub fn export_texture(
&self,
texture: &VulkanTexture,
) -> Result<impeller_hal::ExternalImageDesc> {
use std::os::fd::FromRawFd;
if !texture.usage.scanout {
return Err(Error::Unsupported(
"only an image created for export can be exported",
));
}
let fourcc = texture.format.fourcc().ok_or(Error::Unsupported(
"this format has no scanout representation",
))?;
let modifier = self.texture_modifier(texture)?;
let memory = match &texture.memory {
TextureMemory::Dedicated(memory) => *memory,
TextureMemory::Pooled(_) => {
return Err(Error::Unsupported(
"a suballocated image cannot be exported on its own",
))
}
TextureMemory::Borrowed => {
return Err(Error::Unsupported(
"an image this backend did not allocate cannot be exported",
))
}
};
let loader =
ash::khr::external_memory_fd::Device::new(self.raw_instance(), self.raw_device());
let info = vk::MemoryGetFdInfoKHR::default()
.memory(memory)
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let raw =
unsafe { loader.get_memory_fd(&info) }.map_err(|e| backend_err("get_memory_fd", e))?;
let fd = unsafe { std::os::fd::OwnedFd::from_raw_fd(raw) };
let subresource =
vk::ImageSubresource::default().aspect_mask(vk::ImageAspectFlags::MEMORY_PLANE_0_EXT);
let layout = unsafe {
self.raw_device()
.get_image_subresource_layout(texture.image, subresource)
};
Ok(impeller_hal::ExternalImageDesc {
planes: vec![impeller_hal::DmaBufPlane {
fd,
offset: layout.offset as u32,
stride: layout.row_pitch as u32,
}],
fourcc,
modifier,
})
}
fn find_memory_type(&self, type_bits: u32, flags: vk::MemoryPropertyFlags) -> Option<u32> {
let properties = unsafe {
self.raw_instance()
.get_physical_device_memory_properties(self.raw_physical_device())
};
(0..properties.memory_type_count).find(|i| {
let usable = type_bits & (1 << i) != 0;
usable
&& properties.memory_types[*i as usize]
.property_flags
.contains(flags)
})
}
}
pub fn render_formats(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> Vec<FormatModifierSet> {
[
PixelFormat::Bgra8Unorm,
PixelFormat::Rgba8Unorm,
PixelFormat::Rgb10A2Unorm,
]
.into_iter()
.filter_map(|format| query_format_modifiers(instance, physical_device, format))
.collect()
}