use std::os::fd::{AsRawFd, IntoRawFd, OwnedFd};
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context as _, Result};
use ash::vk;
use gpui_engine::{DmaBufHandle, DmaBufPlane, RgbOrder, SampleDepth, Subsampling};
#[derive(Clone)]
pub(crate) struct PlaneTexture {
#[allow(dead_code, reason = "the view samples the image, but this keeps it alive")]
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
}
pub(crate) fn import_dmabuf(
device: &wgpu::Device,
handle: &DmaBufHandle,
) -> Result<Vec<PlaneTexture>> {
let format = handle.format;
let dimensions = format.dimensions();
let expected = dimensions.plane_count();
anyhow::ensure!(
handle.planes.len() == expected,
"{format} needs {expected} plane(s), but the handle carries {}",
handle.planes.len(),
);
let hal = unsafe { device.as_hal::<wgpu::hal::vulkan::Api>() }
.context("importing a dma-buf needs the Vulkan backend")?;
let instance = hal.shared_instance().raw_instance();
let physical = hal.raw_physical_device();
if !dimensions.is_yuv() {
let (wgpu_format, vk_format) = match (dimensions.rgb_order(), dimensions.depth()) {
(RgbOrder::Bgr, SampleDepth::Bits8) => {
(wgpu::TextureFormat::Bgra8Unorm, vk::Format::B8G8R8A8_UNORM)
}
(RgbOrder::Rgb, SampleDepth::Bits8) => {
(wgpu::TextureFormat::Rgba8Unorm, vk::Format::R8G8B8A8_UNORM)
}
_ => anyhow::bail!("unsupported RGB surface format: {format}"),
};
return Ok(vec![import_plane(
device,
&hal,
instance,
physical,
&handle.planes[0],
handle.modifier,
vk_format,
wgpu_format,
handle.width,
handle.height,
)?]);
}
anyhow::ensure!(
dimensions.subsampling() == Subsampling::C420 && dimensions.depth() == SampleDepth::Bits8,
"unsupported YCbCr surface format: {format}",
);
import_luma_chroma(device, &hal, instance, physical, handle)
}
fn import_luma_chroma(
device: &wgpu::Device,
hal: &wgpu::hal::vulkan::Device,
instance: &ash::Instance,
physical: vk::PhysicalDevice,
handle: &DmaBufHandle,
) -> Result<Vec<PlaneTexture>> {
let raw = hal.raw_device();
let luma = &handle.planes[0];
let chroma = &handle.planes[1];
let tiled = handle.modifier != DmaBufHandle::LINEAR;
let plane_layouts = [
vk::SubresourceLayout::default()
.offset(0)
.row_pitch(u64::from(luma.stride)),
vk::SubresourceLayout::default()
.offset(chroma.offset.saturating_sub(luma.offset))
.row_pitch(u64::from(chroma.stride)),
];
let mut modifier_layout = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
.drm_format_modifier(handle.modifier)
.plane_layouts(&plane_layouts);
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::G8_B8R8_2PLANE_420_UNORM)
.extent(vk::Extent3D {
width: handle.width,
height: handle.height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(if tiled {
vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT
} else {
vk::ImageTiling::LINEAR
})
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image_info = if tiled {
image_info.push_next(&mut modifier_layout)
} else {
image_info
};
let image =
unsafe { raw.create_image(&image_info, None) }.context("create the imported image")?;
let requirements = unsafe { raw.get_image_memory_requirements(image) };
let fd = luma
.fd
.try_clone()
.context("duplicate the plane descriptor for the import")?
.into_raw_fd();
let memory = import_memory(
instance,
physical,
raw,
image,
requirements,
luma.offset,
fd,
)?;
unsafe { raw.bind_image_memory(image, memory, luma.offset) }
.context("bind the imported image at the object's base")?;
let size = wgpu::Extent3d {
width: handle.width,
height: handle.height,
depth_or_array_layers: 1,
};
let hal_texture = unsafe {
hal.texture_from_raw(
image,
&wgpu::hal::TextureDescriptor {
label: Some("dma-buf surface"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUses::RESOURCE,
memory_flags: wgpu::hal::MemoryFlags::empty(),
view_formats: vec![wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm],
},
None,
wgpu::hal::vulkan::TextureMemory::Dedicated(memory),
)
};
let texture = unsafe {
device.create_texture_from_hal::<wgpu::hal::vulkan::Api>(
hal_texture,
&wgpu::TextureDescriptor {
label: Some("dma-buf surface"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::NV12,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[wgpu::TextureFormat::R8Unorm, wgpu::TextureFormat::Rg8Unorm],
},
)
};
let luma_view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("dma-buf surface luma"),
format: Some(wgpu::TextureFormat::R8Unorm),
aspect: wgpu::TextureAspect::Plane0,
..Default::default()
});
let chroma_view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("dma-buf surface chroma"),
format: Some(wgpu::TextureFormat::Rg8Unorm),
aspect: wgpu::TextureAspect::Plane1,
..Default::default()
});
Ok(vec![
PlaneTexture {
texture: texture.clone(),
view: luma_view,
},
PlaneTexture {
texture,
view: chroma_view,
},
])
}
pub(crate) const ACQUIRE_FENCE_TIMEOUT: Duration = Duration::from_millis(16);
pub(crate) fn wait_for_acquire_fence(fence: &OwnedFd, timeout: Duration) -> bool {
let timeout_ms = i32::try_from(timeout.as_millis()).unwrap_or(i32::MAX);
let mut descriptor = libc::pollfd {
fd: fence.as_raw_fd(),
events: libc::POLLIN,
revents: 0,
};
loop {
match unsafe { libc::poll(&mut descriptor, 1, timeout_ms) } {
1.. => return true,
0 => return false,
_ => {
if std::io::Error::last_os_error().kind() == std::io::ErrorKind::Interrupted {
continue;
}
return false;
}
}
}
}
pub(crate) fn signal_release(release: &OwnedFd) {
let counter: u64 = 1;
let written = unsafe {
libc::write(
release.as_raw_fd(),
std::ptr::addr_of!(counter).cast(),
std::mem::size_of::<u64>(),
)
};
if written < 0 {
log::warn!(
"a dma-buf release descriptor did not signal: {}",
std::io::Error::last_os_error()
);
}
}
#[expect(clippy::too_many_arguments)]
fn import_plane(
device: &wgpu::Device,
hal: &wgpu::hal::vulkan::Device,
instance: &ash::Instance,
physical: vk::PhysicalDevice,
plane: &DmaBufPlane,
modifier: u64,
vk_format: vk::Format,
wgpu_format: wgpu::TextureFormat,
width: u32,
height: u32,
) -> Result<PlaneTexture> {
let raw = hal.raw_device();
let tiled = modifier != DmaBufHandle::LINEAR;
let plane_layout = vk::SubresourceLayout::default()
.offset(0)
.row_pitch(u64::from(plane.stride));
let mut modifier_layout = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
.drm_format_modifier(modifier)
.plane_layouts(std::slice::from_ref(&plane_layout));
let image_info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk_format)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(if tiled {
vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT
} else {
vk::ImageTiling::LINEAR
})
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image_info = if tiled {
image_info.push_next(&mut modifier_layout)
} else {
image_info
};
let image =
unsafe { raw.create_image(&image_info, None) }.context("create the imported image")?;
let requirements = unsafe { raw.get_image_memory_requirements(image) };
let fd = plane
.fd
.try_clone()
.context("duplicate the plane descriptor for the import")?
.into_raw_fd();
let memory = import_memory(
instance,
physical,
raw,
image,
requirements,
plane.offset,
fd,
)?;
unsafe { raw.bind_image_memory(image, memory, plane.offset) }
.context("bind the imported image at the plane's offset")?;
let size = wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
};
let hal_texture = unsafe {
hal.texture_from_raw(
image,
&wgpu::hal::TextureDescriptor {
label: Some("dma-buf surface plane"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu_format,
usage: wgpu::TextureUses::RESOURCE,
memory_flags: wgpu::hal::MemoryFlags::empty(),
view_formats: vec![],
},
None,
wgpu::hal::vulkan::TextureMemory::Dedicated(memory),
)
};
let texture = unsafe {
device.create_texture_from_hal::<wgpu::hal::vulkan::Api>(
hal_texture,
&wgpu::TextureDescriptor {
label: Some("dma-buf surface plane"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu_format,
usage: wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
},
)
};
let view = texture.create_view(&wgpu::TextureViewDescriptor {
label: Some("dma-buf surface plane view"),
..Default::default()
});
Ok(PlaneTexture { texture, view })
}
fn import_memory(
instance: &ash::Instance,
physical: vk::PhysicalDevice,
raw: &ash::Device,
image: vk::Image,
requirements: vk::MemoryRequirements,
offset: u64,
fd: std::os::fd::RawFd,
) -> Result<vk::DeviceMemory> {
let properties = unsafe { instance.get_physical_device_memory_properties(physical) };
let mut last_error = None;
for index in 0..properties.memory_type_count {
if requirements.memory_type_bits & (1 << index) == 0 {
continue;
}
let mut import_info = vk::ImportMemoryFdInfoKHR::default()
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
.fd(fd);
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
let allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(offset + requirements.size)
.memory_type_index(index)
.push_next(&mut import_info)
.push_next(&mut dedicated);
match unsafe { raw.allocate_memory(&allocate_info, None) } {
Ok(memory) => return Ok(memory),
Err(error) => last_error = Some(error),
}
}
Err(anyhow::anyhow!(
"no memory type accepted the dma-buf import (last: {last_error:?})",
))
}
pub(crate) struct DmaBufTextureCache {
entries: Vec<CacheEntry>,
frame: u64,
}
struct CacheEntry {
handle: DmaBufHandle,
textures: Vec<PlaneTexture>,
last_used: u64,
}
const CAPACITY: usize = 8;
impl DmaBufTextureCache {
pub(crate) fn new() -> Self {
Self {
entries: Vec::new(),
frame: 0,
}
}
pub(crate) fn textures(
&mut self,
device: &wgpu::Device,
handle: &DmaBufHandle,
) -> Result<Vec<PlaneTexture>> {
self.frame += 1;
let frame = self.frame;
if let Some(index) = self.find(handle) {
self.entries[index].last_used = frame;
return Ok(self.entries[index].textures.clone());
}
let imported = import_dmabuf(device, handle)?;
self.evict_if_full();
self.entries.push(CacheEntry {
handle: handle.clone(),
textures: imported.clone(),
last_used: frame,
});
Ok(imported)
}
fn find(&self, handle: &DmaBufHandle) -> Option<usize> {
self.entries
.iter()
.position(|entry| same_buffer(&entry.handle, handle))
}
fn evict_if_full(&mut self) {
if self.entries.len() < CAPACITY {
return;
}
if let Some((index, _)) = self
.entries
.iter()
.enumerate()
.min_by_key(|(_, entry)| entry.last_used)
{
self.entries.swap_remove(index);
}
}
}
fn same_buffer(a: &DmaBufHandle, b: &DmaBufHandle) -> bool {
a.width == b.width
&& a.height == b.height
&& a.format == b.format
&& a.modifier == b.modifier
&& a.planes.len() == b.planes.len()
&& a.planes
.iter()
.zip(&b.planes)
.all(|(a, b)| Arc::ptr_eq(&a.fd, &b.fd) && a.offset == b.offset && a.stride == b.stride)
}
#[cfg(test)]
mod tests {
use super::*;
use gpui_engine::SurfaceFormatKind;
use std::os::fd::OwnedFd;
fn descriptor() -> OwnedFd {
std::fs::File::open("/dev/null").expect("/dev/null").into()
}
fn handle(width: u32, offset: u64, stride: u32) -> DmaBufHandle {
DmaBufHandle::new(
width,
2,
SurfaceFormatKind::bgra8(),
DmaBufHandle::LINEAR,
[DmaBufPlane::new(descriptor(), offset, stride)],
None,
)
}
#[test]
fn a_cloned_handle_is_the_same_buffer() {
let handle = handle(4, 0, 16);
assert!(same_buffer(&handle, &handle.clone()));
}
#[test]
fn layout_and_format_differences_are_different_buffers() {
let base = handle(4, 0, 16);
assert!(!same_buffer(&base, &handle(8, 0, 16)), "width");
assert!(!same_buffer(&base, &handle(4, 16, 16)), "offset");
assert!(!same_buffer(&base, &handle(4, 0, 32)), "stride");
let rgba = DmaBufHandle::new(
4,
2,
SurfaceFormatKind::rgba8(),
DmaBufHandle::LINEAR,
[DmaBufPlane::new(descriptor(), 0, 16)],
None,
);
assert!(!same_buffer(&base, &rgba), "format");
}
#[test]
fn a_different_descriptor_is_a_different_buffer() {
assert!(!same_buffer(&handle(4, 0, 16), &handle(4, 0, 16)));
}
#[test]
fn the_fence_does_not_affect_identity() {
let handle = handle(4, 0, 16);
let mut with_fence = handle.clone();
with_fence.acquire_fence = Some(Arc::new(descriptor()));
assert!(same_buffer(&handle, &with_fence));
}
fn entry(handle: DmaBufHandle, last_used: u64) -> CacheEntry {
CacheEntry {
handle,
textures: Vec::new(),
last_used,
}
}
#[test]
fn find_locates_the_matching_buffer() {
let buffer = handle(4, 0, 16);
let other = handle(8, 0, 16);
let mut cache = DmaBufTextureCache::new();
assert_eq!(cache.find(&buffer), None);
cache.entries.push(entry(buffer.clone(), 1));
cache.entries.push(entry(other.clone(), 1));
assert_eq!(cache.find(&buffer), Some(0));
assert_eq!(cache.find(&other), Some(1));
}
#[test]
fn a_full_cache_evicts_the_least_recently_used() {
let buffers: Vec<_> = (0..CAPACITY)
.map(|i| handle((i + 1) as u32, 0, 16))
.collect();
let mut cache = DmaBufTextureCache::new();
for (i, buffer) in buffers.iter().enumerate() {
cache.entries.push(entry(buffer.clone(), i as u64));
}
for entry in &mut cache.entries[1..] {
entry.last_used = 100;
}
cache.evict_if_full();
assert_eq!(cache.entries.len(), CAPACITY - 1);
assert!(
cache.find(&buffers[0]).is_none(),
"the oldest entry was evicted"
);
for buffer in &buffers[1..] {
assert!(cache.find(buffer).is_some());
}
}
#[test]
fn a_cache_with_room_evicts_nothing() {
let mut cache = DmaBufTextureCache::new();
cache.entries.push(entry(handle(4, 0, 16), 1));
cache.evict_if_full();
assert_eq!(cache.entries.len(), 1);
}
#[test]
fn an_already_signalled_fence_does_not_block() {
let fence: OwnedFd = std::fs::File::open("/dev/null").expect("/dev/null").into();
assert!(wait_for_acquire_fence(&fence, Duration::from_millis(100)));
}
#[test]
fn a_fence_that_never_signals_times_out() {
let (read_end, _write_end) = std::io::pipe().expect("a pipe");
let fence = OwnedFd::from(read_end);
let started = std::time::Instant::now();
assert!(!wait_for_acquire_fence(&fence, Duration::from_millis(10)));
assert!(
started.elapsed() >= Duration::from_millis(5),
"it should have waited"
);
}
}