use std::{
ffi::CStr,
os::fd::{AsRawFd, FromRawFd, OwnedFd},
sync::Arc,
};
use anyhow::{Result, anyhow, bail};
use ash::vk;
use crate::{Renderer, Size};
pub struct ExportableVulkanTexture {
texture: Arc<wgpu::Texture>,
memory_fd: OwnedFd,
allocation_size: u64,
row_pitch: u64,
device_info: VulkanDeviceInfo,
memory_type_index: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VulkanDeviceInfo {
pub name: String,
pub vendor_id: u32,
pub device_id: u32,
pub device_type: String,
pub device_uuid: [u8; 16],
pub driver_uuid: [u8; 16],
}
impl ExportableVulkanTexture {
pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
pub fn texture_arc(&self) -> Arc<wgpu::Texture> {
Arc::clone(&self.texture)
}
pub fn memory_fd(&self) -> &OwnedFd {
&self.memory_fd
}
pub fn memory_fd_raw(&self) -> i32 {
self.memory_fd.as_raw_fd()
}
pub fn allocation_size(&self) -> u64 {
self.allocation_size
}
pub fn row_pitch(&self) -> u64 {
self.row_pitch
}
pub fn device_info(&self) -> &VulkanDeviceInfo {
&self.device_info
}
pub fn memory_type_index(&self) -> u32 {
self.memory_type_index
}
}
impl Renderer {
pub fn create_exportable_vulkan_texture(
&self,
label: Option<&str>,
size: Size,
format: wgpu::TextureFormat,
usage: wgpu::TextureUsages,
) -> Result<ExportableVulkanTexture> {
let Some(hal_device) = (unsafe { self.device.as_hal::<wgpu_hal::api::Vulkan>() }) else {
bail!("wgpu device is not using the Vulkan backend");
};
let hal_device = &*hal_device;
let raw_device = hal_device.raw_device();
let raw_instance = hal_device.shared_instance().raw_instance();
let physical_device = hal_device.raw_physical_device();
let device_info = vulkan_device_info(raw_instance, physical_device);
let vk_format = map_texture_format(format)?;
let vk_extent = vk::Extent3D {
width: size.width,
height: size.height,
depth: 1,
};
let mut external_image = vk::ExternalMemoryImageCreateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
let image_info = vk::ImageCreateInfo::default()
.push_next(&mut external_image)
.image_type(vk::ImageType::TYPE_2D)
.format(vk_format)
.extent(vk_extent)
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::OPTIMAL)
.usage(map_texture_usage(usage))
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image = unsafe { raw_device.create_image(&image_info, None) }
.map_err(|err| anyhow!("vkCreateImage for exportable texture failed: {err:?}"))?;
let requirements = unsafe { raw_device.get_image_memory_requirements(image) };
let memory_type_index = find_device_local_memory_type(
raw_instance,
physical_device,
requirements.memory_type_bits,
)?;
let mut export_allocate = vk::ExportMemoryAllocateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
let allocate_info = vk::MemoryAllocateInfo::default()
.push_next(&mut export_allocate)
.allocation_size(requirements.size)
.memory_type_index(memory_type_index);
let memory = match unsafe { raw_device.allocate_memory(&allocate_info, None) } {
Ok(memory) => memory,
Err(err) => {
unsafe { raw_device.destroy_image(image, None) };
return Err(anyhow!(
"vkAllocateMemory for exportable texture failed: {err:?}"
));
}
};
if let Err(err) = unsafe { raw_device.bind_image_memory(image, memory, 0) } {
unsafe {
raw_device.free_memory(memory, None);
raw_device.destroy_image(image, None);
}
return Err(anyhow!(
"vkBindImageMemory for exportable texture failed: {err:?}"
));
}
let memory_fd = match export_memory_fd(raw_instance, raw_device, memory) {
Ok(fd) => fd,
Err(err) => {
unsafe {
raw_device.free_memory(memory, None);
raw_device.destroy_image(image, None);
}
return Err(err);
}
};
let hal_desc = wgpu_hal::TextureDescriptor {
label,
size: wgpu::Extent3d {
width: size.width,
height: size.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage: map_hal_texture_usage(usage),
memory_flags: wgpu_hal::MemoryFlags::empty(),
view_formats: Vec::new(),
};
let wgpu_desc = wgpu::TextureDescriptor {
label,
size: hal_desc.size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format,
usage,
view_formats: &[],
};
let drop_device = raw_device.clone();
let drop_callback: wgpu_hal::DropCallback = Box::new(move || unsafe {
drop_device.free_memory(memory, None);
drop_device.destroy_image(image, None);
});
let hal_texture = unsafe {
hal_device.texture_from_raw(
image,
&hal_desc,
Some(drop_callback),
wgpu_hal::vulkan::TextureMemory::External,
)
};
let texture = unsafe {
self.device
.create_texture_from_hal::<wgpu_hal::api::Vulkan>(hal_texture, &wgpu_desc)
};
Ok(ExportableVulkanTexture {
texture: Arc::new(texture),
memory_fd,
allocation_size: requirements.size,
row_pitch: u64::from(size.width).saturating_mul(4),
device_info,
memory_type_index,
})
}
}
fn vulkan_device_info(
raw_instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
) -> VulkanDeviceInfo {
let properties = unsafe { raw_instance.get_physical_device_properties(physical_device) };
let mut id_properties = vk::PhysicalDeviceIDProperties::default();
let mut properties2 = vk::PhysicalDeviceProperties2::default().push_next(&mut id_properties);
unsafe {
raw_instance.get_physical_device_properties2(physical_device, &mut properties2);
}
VulkanDeviceInfo {
name: unsafe { CStr::from_ptr(properties.device_name.as_ptr()) }
.to_string_lossy()
.into_owned(),
vendor_id: properties.vendor_id,
device_id: properties.device_id,
device_type: format!("{:?}", properties.device_type),
device_uuid: id_properties.device_uuid,
driver_uuid: id_properties.driver_uuid,
}
}
fn export_memory_fd(
raw_instance: &ash::Instance,
raw_device: &ash::Device,
memory: vk::DeviceMemory,
) -> Result<OwnedFd> {
let external_memory_fd = ash::khr::external_memory_fd::Device::new(raw_instance, raw_device);
let get_fd_info = vk::MemoryGetFdInfoKHR::default()
.memory(memory)
.handle_type(vk::ExternalMemoryHandleTypeFlags::OPAQUE_FD);
let fd = unsafe { external_memory_fd.get_memory_fd(&get_fd_info) }
.map_err(|err| anyhow!("vkGetMemoryFdKHR failed: {err:?}"))?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
fn find_device_local_memory_type(
raw_instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
type_bits: u32,
) -> Result<u32> {
let memory_properties =
unsafe { raw_instance.get_physical_device_memory_properties(physical_device) };
for index in 0..memory_properties.memory_type_count {
let supported = (type_bits & (1 << index)) != 0;
let flags = memory_properties.memory_types[index as usize].property_flags;
if supported && flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) {
return Ok(index);
}
}
Err(anyhow!(
"no DEVICE_LOCAL Vulkan memory type matched exportable image requirements"
))
}
fn map_texture_format(format: wgpu::TextureFormat) -> Result<vk::Format> {
match format {
wgpu::TextureFormat::Rgba8Unorm => Ok(vk::Format::R8G8B8A8_UNORM),
wgpu::TextureFormat::Bgra8Unorm => Ok(vk::Format::B8G8R8A8_UNORM),
other => Err(anyhow!(
"exportable Vulkan texture format {other:?} is not mapped"
)),
}
}
fn map_texture_usage(usage: wgpu::TextureUsages) -> vk::ImageUsageFlags {
let mut flags = vk::ImageUsageFlags::empty();
if usage.contains(wgpu::TextureUsages::COPY_SRC) {
flags |= vk::ImageUsageFlags::TRANSFER_SRC;
}
if usage.contains(wgpu::TextureUsages::COPY_DST) {
flags |= vk::ImageUsageFlags::TRANSFER_DST;
}
if usage.contains(wgpu::TextureUsages::TEXTURE_BINDING) {
flags |= vk::ImageUsageFlags::SAMPLED;
}
if usage.contains(wgpu::TextureUsages::STORAGE_BINDING) {
flags |= vk::ImageUsageFlags::STORAGE;
}
if usage.contains(wgpu::TextureUsages::RENDER_ATTACHMENT) {
flags |= vk::ImageUsageFlags::COLOR_ATTACHMENT;
}
flags
}
fn map_hal_texture_usage(usage: wgpu::TextureUsages) -> wgpu::TextureUses {
let mut uses = wgpu::TextureUses::empty();
if usage.contains(wgpu::TextureUsages::COPY_SRC) {
uses |= wgpu::TextureUses::COPY_SRC;
}
if usage.contains(wgpu::TextureUsages::COPY_DST) {
uses |= wgpu::TextureUses::COPY_DST;
}
if usage.contains(wgpu::TextureUsages::TEXTURE_BINDING) {
uses |= wgpu::TextureUses::RESOURCE;
}
if usage.contains(wgpu::TextureUsages::STORAGE_BINDING) {
uses |= wgpu::TextureUses::STORAGE_WRITE_ONLY;
}
if usage.contains(wgpu::TextureUsages::RENDER_ATTACHMENT) {
uses |= wgpu::TextureUses::COLOR_TARGET;
}
uses
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn maps_rgba8_exportable_format() {
assert_eq!(
map_texture_format(wgpu::TextureFormat::Rgba8Unorm).unwrap(),
vk::Format::R8G8B8A8_UNORM
);
}
#[test]
fn maps_copy_destination_usage() {
let usage = map_texture_usage(
wgpu::TextureUsages::COPY_DST
| wgpu::TextureUsages::COPY_SRC
| wgpu::TextureUsages::TEXTURE_BINDING,
);
assert!(usage.contains(vk::ImageUsageFlags::TRANSFER_DST));
assert!(usage.contains(vk::ImageUsageFlags::TRANSFER_SRC));
assert!(usage.contains(vk::ImageUsageFlags::SAMPLED));
}
}