use std::io::{Seek as _, SeekFrom};
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::{
DmaBufCapabilities, DmaBufFormatCapability, DmaBufHandle, DmaBufPlane, RgbOrder, SampleDepth,
Subsampling, SurfaceFormatKind,
};
#[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)
}
pub fn dmabuf_capabilities(device: &wgpu::Device) -> Option<DmaBufCapabilities> {
let hal = unsafe { device.as_hal::<wgpu::hal::vulkan::Api>() }?;
let instance = hal.shared_instance().raw_instance();
let physical = hal.raw_physical_device();
let tiled = honours_plane_layouts(&hal);
let formats = importable_formats()
.into_iter()
.map(|(format, vk_format)| {
let properties =
unsafe { instance.get_physical_device_format_properties(physical, vk_format) };
let mut modifiers = Vec::new();
if properties
.linear_tiling_features
.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE)
{
modifiers.push(DmaBufHandle::LINEAR);
}
if tiled {
for properties in drm_modifiers(instance, physical, vk_format) {
if properties
.drm_format_modifier_tiling_features
.contains(vk::FormatFeatureFlags::SAMPLED_IMAGE)
&& !modifiers.contains(&properties.drm_format_modifier)
{
modifiers.push(properties.drm_format_modifier);
}
}
}
DmaBufFormatCapability { format, modifiers }
})
.collect();
Some(DmaBufCapabilities::new(formats))
}
fn importable_formats() -> [(SurfaceFormatKind, vk::Format); 4] {
[
(SurfaceFormatKind::bgra8(), vk::Format::B8G8R8A8_UNORM),
(SurfaceFormatKind::rgba8(), vk::Format::R8G8B8A8_UNORM),
(
SurfaceFormatKind::nv12(),
vk::Format::G8_B8R8_2PLANE_420_UNORM,
),
(
SurfaceFormatKind::nv21(),
vk::Format::G8_B8R8_2PLANE_420_UNORM,
),
]
}
fn drm_modifiers(
instance: &ash::Instance,
physical: vk::PhysicalDevice,
format: vk::Format,
) -> Vec<vk::DrmFormatModifierPropertiesEXT> {
unsafe {
let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
let mut properties = vk::FormatProperties2::default().push_next(&mut list);
instance.get_physical_device_format_properties2(physical, format, &mut properties);
let count = list.drm_format_modifier_count as usize;
if count == 0 {
return Vec::new();
}
let mut modifiers = vec![vk::DrmFormatModifierPropertiesEXT::default(); count];
let mut list = vk::DrmFormatModifierPropertiesListEXT::default()
.drm_format_modifier_properties(&mut modifiers);
let mut properties = vk::FormatProperties2::default().push_next(&mut list);
instance.get_physical_device_format_properties2(physical, format, &mut properties);
modifiers
}
}
fn honours_plane_layouts(hal: &wgpu::hal::vulkan::Device) -> bool {
hal.enabled_device_extensions()
.contains(&vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
}
fn ensure_native_layout(
raw: &ash::Device,
format: vk::Format,
width: u32,
height: u32,
aspects: &[vk::ImageAspectFlags],
declared: &[vk::SubresourceLayout],
) -> Result<()> {
let probe = create_plain_linear_image(raw, format, width, height)?;
for (index, (aspect, declared)) in aspects.iter().zip(declared).enumerate() {
let subresource = vk::ImageSubresource {
aspect_mask: *aspect,
mip_level: 0,
array_layer: 0,
};
let native = unsafe { raw.get_image_subresource_layout(probe, subresource) };
if native.offset != declared.offset {
unsafe { raw.destroy_image(probe, None) };
anyhow::bail!(
"the renderer's device lays a {format:?} {width}×{height} linear image's plane {index} \
out at offset {}, and the handle declares offset {}. This device cannot sample a \
layout it did not choose — export the layout it reports for a plain linear image, or \
a tiled modifier it lists",
native.offset,
declared.offset,
);
}
}
unsafe { raw.destroy_image(probe, None) };
Ok(())
}
fn create_plain_linear_image(
raw: &ash::Device,
format: vk::Format,
width: u32,
height: u32,
) -> Result<vk::Image> {
let info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(format)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(vk::ImageTiling::LINEAR)
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
unsafe { raw.create_image(&info, None) }.context("create a plain linear layout probe")
}
fn ensure_buffer_backs_image(
raw: &ash::Device,
image: vk::Image,
requirements: vk::MemoryRequirements,
bind_offset: u64,
fd: &OwnedFd,
) -> Result<()> {
let available = buffer_size(fd)?;
let needed = bind_offset + requirements.size;
if needed > available {
unsafe { raw.destroy_image(image, None) };
anyhow::bail!(
"the renderer's device needs {needed} bytes for an image bound at offset {bind_offset}, \
but the dma-buf holds {available}: this device cannot back the declared layout. Export a \
buffer of at least {needed} bytes, or one whose plane layout it backs with what it has",
);
}
Ok(())
}
fn buffer_size(fd: &OwnedFd) -> Result<u64> {
let mut file = std::fs::File::from(fd.try_clone().context("duplicate the descriptor")?);
file.seek(SeekFrom::End(0)).context("measure the dma-buf")
}
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 explicit = honours_plane_layouts(hal);
anyhow::ensure!(
explicit || !tiled,
"a tiled Nv12 dma-buf needs VK_EXT_image_drm_format_modifier on the renderer's device, and \
this one does not have it",
);
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)),
];
if !tiled {
ensure_native_layout(
raw,
vk::Format::G8_B8R8_2PLANE_420_UNORM,
handle.width,
handle.height,
&[vk::ImageAspectFlags::PLANE_0, vk::ImageAspectFlags::PLANE_1],
&plane_layouts,
)?;
}
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 explicit {
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 explicit {
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) };
ensure_buffer_backs_image(raw, image, requirements, luma.offset, &luma.fd)?;
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 explicit = honours_plane_layouts(hal);
anyhow::ensure!(
explicit || !tiled,
"a tiled dma-buf needs VK_EXT_image_drm_format_modifier on the renderer's device, and this \
one does not have it",
);
let plane_layout = vk::SubresourceLayout::default()
.offset(0)
.row_pitch(u64::from(plane.stride));
if !tiled {
ensure_native_layout(
raw,
vk_format,
width,
height,
&[vk::ImageAspectFlags::COLOR],
&[plane_layout],
)?;
}
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 explicit {
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 explicit {
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) };
ensure_buffer_backs_image(raw, image, requirements, plane.offset, &plane.fd)?;
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"
);
}
}