use std::ffi::{CStr, CString};
use std::os::fd::{FromRawFd, OwnedFd};
use std::os::raw::c_char;
use std::path::PathBuf;
use std::sync::OnceLock;
use anyhow::{Result, anyhow};
use ash::vk;
use gpui_engine::{DmaBufHandle, DmaBufPlane, SurfaceFormatKind, TransferFunction};
const DEVICE_EXTENSIONS: [&str; 4] = [
"VK_KHR_external_memory_fd",
"VK_EXT_external_memory_dma_buf",
"VK_EXT_image_drm_format_modifier",
"VK_KHR_external_fence_fd",
];
const DRM_EXTENSION: &str = "VK_EXT_physical_device_drm";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Tiling {
Linear,
Tiled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PlaneLayout {
pub offset: u64,
pub stride: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinearLayout {
pub native: Vec<PlaneLayout>,
pub required: u64,
pub declared: u64,
}
impl LinearLayout {
pub fn backed(&self) -> bool {
self.required <= self.declared
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GpuId {
pub name: String,
pub render_node: Option<PathBuf>,
}
impl GpuId {
pub(crate) fn of(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> Self {
unsafe {
let properties = instance.get_physical_device_properties(physical_device);
let name = extension_name(&properties.device_name).to_owned();
let mut drm = vk::PhysicalDeviceDrmPropertiesEXT::default();
let mut properties = vk::PhysicalDeviceProperties2::default().push_next(&mut drm);
instance.get_physical_device_properties2(physical_device, &mut properties);
let render_node = (drm.has_render != 0)
.then(|| PathBuf::from(format!("/dev/dri/renderD{}", drm.render_minor)));
Self { name, render_node }
}
}
#[cfg(feature = "gpui")]
pub fn from_adapter(info: &gpui::GpuAdapterInfo) -> Option<Self> {
if let Some(gpui::SessionGpuId::Drm { render_minor }) = info.id.session {
return Some(Self {
name: info.name.clone(),
render_node: Some(PathBuf::from(format!("/dev/dri/renderD{render_minor}"))),
});
}
let vendor_id = info.vendor_id()?;
let device_id = info.device_id()?;
if device_id == 0 {
return None;
}
let _loading = LOADER.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
let entry = unsafe { ash::Entry::load() }.ok()?;
let instance = create_instance(&entry).ok()?;
let devices = unsafe { instance.enumerate_physical_devices() }.ok()?;
let physical_device = devices.into_iter().find(|physical_device| {
let properties =
unsafe { instance.get_physical_device_properties(*physical_device) };
properties.vendor_id == vendor_id && properties.device_id == device_id
})?;
Some(Self::of(&instance, physical_device))
}
#[cfg(feature = "gpui")]
pub fn matches_adapter(&self, info: &gpui::GpuAdapterInfo) -> bool {
self.name == info.name
}
}
static RENDERER_GPU: OnceLock<GpuId> = OnceLock::new();
pub fn remember(id: GpuId) {
let _ = RENDERER_GPU.set(id);
}
pub fn gpu_id() -> Option<GpuId> {
RENDERER_GPU.get().cloned()
}
pub struct GpuDevice {
_entry: Option<ash::Entry>,
instance: ash::Instance,
owns_instance: bool,
renderer_bound: bool,
physical_device: vk::PhysicalDevice,
device: ash::Device,
external_memory_fd: ash::khr::external_memory_fd::Device,
drm_format_modifier: ash::ext::image_drm_format_modifier::Device,
queue: vk::Queue,
queue_family_index: u32,
command_pool: vk::CommandPool,
}
static LOADER: std::sync::Mutex<()> = std::sync::Mutex::new(());
impl GpuDevice {
pub fn open() -> Result<Self> {
let _loading = LOADER
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let entry = unsafe { ash::Entry::load() }.map_err(|e| anyhow!("load libvulkan: {e}"))?;
let instance = create_instance(&entry)?;
let (physical_device, queue_family_index) = pick_device(&instance).ok_or_else(|| {
anyhow!("no Vulkan device exports a dma-buf (needs {DEVICE_EXTENSIONS:?})")
})?;
Self::assemble(
Some(entry),
instance,
true,
false,
physical_device,
queue_family_index,
)
}
pub fn on(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> Result<Self> {
ensure_extensions(instance, physical_device)?;
let queue_family_index = render_family(instance, physical_device)
.ok_or_else(|| anyhow!("the renderer's device has no graphics queue family"))?;
Self::assemble(
None,
instance.clone(),
false,
true,
physical_device,
queue_family_index,
)
}
fn assemble(
entry: Option<ash::Entry>,
instance: ash::Instance,
owns_instance: bool,
renderer_bound: bool,
physical_device: vk::PhysicalDevice,
queue_family_index: u32,
) -> Result<Self> {
let device = create_device(&instance, physical_device, queue_family_index)?;
let external_memory_fd = ash::khr::external_memory_fd::Device::new(&instance, &device);
let drm_format_modifier =
ash::ext::image_drm_format_modifier::Device::new(&instance, &device);
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
let pool_info = vk::CommandPoolCreateInfo::default()
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
.queue_family_index(queue_family_index);
let command_pool = unsafe { device.create_command_pool(&pool_info, None) }
.map_err(|e| anyhow!("create the command pool: {e:?}"))?;
Ok(Self {
_entry: entry,
instance,
owns_instance,
renderer_bound,
physical_device,
device,
external_memory_fd,
drm_format_modifier,
queue,
queue_family_index,
command_pool,
})
}
pub fn device(&self) -> &ash::Device {
&self.device
}
fn instance(&self) -> &ash::Instance {
&self.instance
}
fn physical_device(&self) -> vk::PhysicalDevice {
self.physical_device
}
fn external_memory_fd(&self) -> &ash::khr::external_memory_fd::Device {
&self.external_memory_fd
}
fn drm_format_modifier(&self) -> &ash::ext::image_drm_format_modifier::Device {
&self.drm_format_modifier
}
pub fn queue(&self) -> vk::Queue {
self.queue
}
fn command_pool(&self) -> vk::CommandPool {
self.command_pool
}
pub fn queue_family_index(&self) -> u32 {
self.queue_family_index
}
pub fn gpu_id(&self) -> GpuId {
GpuId::of(&self.instance, self.physical_device)
}
pub fn memory_type(&self, bits: u32, required: vk::MemoryPropertyFlags) -> Result<u32> {
let properties = unsafe {
self.instance
.get_physical_device_memory_properties(self.physical_device)
};
(0..properties.memory_type_count)
.find(|i| {
bits & (1u32 << i) != 0
&& properties.memory_types[*i as usize]
.property_flags
.contains(required)
})
.ok_or_else(|| anyhow!("no memory type carrying {required:?} for the image"))
}
pub fn linear_layout(
&self,
format: SurfaceFormatKind,
width: u32,
height: u32,
planes: &[PlaneLayout],
) -> Result<LinearLayout> {
let vk_format = vk_format(format)?;
let geometry = plane_geometry(format, width, height);
anyhow::ensure!(!geometry.is_empty(), "{format:?} has no plane layout to report on");
anyhow::ensure!(
geometry.len() == planes.len(),
"{format:?} has {} planes, but {} layouts were given",
geometry.len(),
planes.len(),
);
let declared = declared_span(&geometry, planes)?;
let native = self.native_plane_layouts(vk_format, width, height, geometry.len())?;
let layouts: Vec<vk::SubresourceLayout> = native
.iter()
.map(|plane| {
vk::SubresourceLayout::default()
.offset(plane.offset)
.row_pitch(u64::from(plane.stride))
})
.collect();
let mut explicit = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
.drm_format_modifier(DmaBufHandle::LINEAR)
.plane_layouts(&layouts);
let 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(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.push_next(&mut explicit);
let image = unsafe { self.device.create_image(&info, None) }.map_err(|error| {
anyhow!("the device will not make a {format:?} image with these plane layouts: {error:?}")
})?;
let required = unsafe { self.device.get_image_memory_requirements(image) }.size;
unsafe { self.device.destroy_image(image, None) };
Ok(LinearLayout {
native,
required,
declared,
})
}
fn native_plane_layouts(
&self,
vk_format: vk::Format,
width: u32,
height: u32,
plane_count: usize,
) -> Result<Vec<PlaneLayout>> {
let 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(vk::ImageTiling::LINEAR)
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
let image = unsafe { self.device.create_image(&info, None) }
.map_err(|error| anyhow!("the device will not make a plain {vk_format:?} image: {error:?}"))?;
let mut native = Vec::with_capacity(plane_count);
for plane in 0..plane_count {
let layout = unsafe {
self.device.get_image_subresource_layout(
image,
vk::ImageSubresource {
aspect_mask: plane_aspect(plane, plane_count),
mip_level: 0,
array_layer: 0,
},
)
};
native.push(PlaneLayout {
offset: layout.offset,
stride: layout.row_pitch as u32,
});
}
unsafe { self.device.destroy_image(image, None) };
Ok(native)
}
}
impl Drop for GpuDevice {
fn drop(&mut self) {
unsafe {
self.device.destroy_command_pool(self.command_pool, None);
self.device.destroy_device(None);
if self.owns_instance {
self.instance.destroy_instance(None);
}
}
}
}
pub(crate) trait ExportDevice {
fn instance(&self) -> &ash::Instance;
fn physical_device(&self) -> vk::PhysicalDevice;
fn device(&self) -> &ash::Device;
fn external_memory_fd(&self) -> &ash::khr::external_memory_fd::Device;
fn drm_format_modifier(&self) -> &ash::ext::image_drm_format_modifier::Device;
fn renderer_bound(&self) -> bool;
fn memory_type(&self, bits: u32, required: vk::MemoryPropertyFlags) -> Result<u32> {
let properties = unsafe {
self.instance()
.get_physical_device_memory_properties(self.physical_device())
};
(0..properties.memory_type_count)
.find(|i| {
bits & (1u32 << i) != 0
&& properties.memory_types[*i as usize]
.property_flags
.contains(required)
})
.ok_or_else(|| anyhow!("no memory type carrying {required:?} for the image"))
}
}
impl ExportDevice for GpuDevice {
fn instance(&self) -> &ash::Instance {
GpuDevice::instance(self)
}
fn physical_device(&self) -> vk::PhysicalDevice {
GpuDevice::physical_device(self)
}
fn device(&self) -> &ash::Device {
GpuDevice::device(self)
}
fn external_memory_fd(&self) -> &ash::khr::external_memory_fd::Device {
GpuDevice::external_memory_fd(self)
}
fn drm_format_modifier(&self) -> &ash::ext::image_drm_format_modifier::Device {
GpuDevice::drm_format_modifier(self)
}
fn renderer_bound(&self) -> bool {
self.renderer_bound
}
fn memory_type(&self, bits: u32, required: vk::MemoryPropertyFlags) -> Result<u32> {
GpuDevice::memory_type(self, bits, required)
}
}
pub(crate) fn allocate_export_image<D: ExportDevice + ?Sized>(
device: &D,
width: u32,
height: u32,
format: SurfaceFormatKind,
vk_format: vk::Format,
tiling: Tiling,
rendered: bool,
) -> Result<(vk::Image, vk::DeviceMemory, DmaBufHandle)> {
let plane_count = format.plane_count();
let usage = usage_for(rendered);
let required = required_features(usage);
let modifier = match tiling {
Tiling::Linear => None,
Tiling::Tiled if !device.renderer_bound() => {
log::warn!(
"gpui_interop: Tiling::Tiled needs the renderer's own device; this one was opened \
by the producer, so the modifier it would pick is not known to be importable. \
Exporting a linear buffer — borrow the renderer's device with \
`Adapter::gpu_device` to tile."
);
None
}
Tiling::Tiled => {
let modifier = tiled_modifier(device, vk_format, required);
if modifier.is_none() {
log::warn!(
"gpui_interop: this device offers no {format:?} modifier the renderer can \
import; exporting a linear buffer"
);
}
modifier
}
};
ensure_image_supported(device, vk_format, width, height, usage, required, modifier)?;
let image = create_image(device, vk_format, width, height, usage, modifier)?;
let requirements = unsafe { device.device().get_image_memory_requirements(image) };
let memory_type_index = device.memory_type(
requirements.memory_type_bits,
vk::MemoryPropertyFlags::DEVICE_LOCAL,
)?;
let mut export = vk::ExportMemoryAllocateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(image);
let allocate_info = vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(memory_type_index)
.push_next(&mut export)
.push_next(&mut dedicated);
let memory = match unsafe { device.device().allocate_memory(&allocate_info, None) } {
Ok(memory) => memory,
Err(e) => {
unsafe { device.device().destroy_image(image, None) };
return Err(anyhow!("allocate the image memory: {e:?}"));
}
};
if let Err(e) = unsafe { device.device().bind_image_memory(image, memory, 0) } {
unsafe { device.device().free_memory(memory, None) };
unsafe { device.device().destroy_image(image, None) };
return Err(anyhow!("bind the image memory: {e:?}"));
}
let result = (|| -> Result<DmaBufHandle> {
let drm = modifier.is_some();
let modifier = match modifier {
Some(_) => {
let mut properties = vk::ImageDrmFormatModifierPropertiesEXT::default();
unsafe {
device
.drm_format_modifier()
.get_image_drm_format_modifier_properties(image, &mut properties)
}
.map_err(|e| anyhow!("read the image's modifier: {e:?}"))?;
properties.drm_format_modifier
}
None => DmaBufHandle::LINEAR,
};
let fd = unsafe {
device.external_memory_fd().get_memory_fd(
&vk::MemoryGetFdInfoKHR::default()
.memory(memory)
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT),
)
}
.map_err(|e| anyhow!("export the dma-buf fd: {e:?}"))?;
let fd = unsafe { OwnedFd::from_raw_fd(fd) };
let mut planes = Vec::with_capacity(plane_count);
for plane in 0..plane_count {
let layout = unsafe {
device.device().get_image_subresource_layout(
image,
vk::ImageSubresource {
aspect_mask: subresource_aspect(drm, plane, plane_count),
mip_level: 0,
array_layer: 0,
},
)
};
let plane_fd = fd
.try_clone()
.map_err(|e| anyhow!("duplicate the dma-buf fd for plane {plane}: {e}"))?;
planes.push(DmaBufPlane::new(
plane_fd,
layout.offset,
layout.row_pitch as u32,
));
}
Ok(DmaBufHandle::new(
width, height, format, modifier, planes, None,
))
})();
match result {
Ok(handle) => Ok((image, memory, handle)),
Err(error) => {
unsafe { device.device().free_memory(memory, None) };
unsafe { device.device().destroy_image(image, None) };
Err(error)
}
}
}
pub struct SharedBuffer {
device: ash::Device,
backing: Backing,
memory: vk::DeviceMemory,
handle: DmaBufHandle,
}
enum Backing {
Image(vk::Image),
Buffer(vk::Buffer),
}
impl SharedBuffer {
pub fn new(
device: &GpuDevice,
width: u32,
height: u32,
format: SurfaceFormatKind,
tiling: Tiling,
) -> Result<Self> {
Self::allocate(device, width, height, format, tiling, false)
}
pub fn render_target(
device: &GpuDevice,
width: u32,
height: u32,
format: SurfaceFormatKind,
tiling: Tiling,
) -> Result<Self> {
Self::allocate(device, width, height, format, tiling, true)
}
pub fn export_at(
device: &GpuDevice,
width: u32,
height: u32,
format: SurfaceFormatKind,
planes: &[PlaneLayout],
content: &[u8],
) -> Result<Self> {
let geometry = plane_geometry(format, width, height);
if geometry.is_empty() {
return Err(anyhow!("{format:?} has no plane layout to export"));
}
if geometry.len() != planes.len() {
return Err(anyhow!(
"{format:?} has {} planes, but {} layouts were given",
geometry.len(),
planes.len(),
));
}
check_plane_layouts(&geometry, planes, content.len())?;
let size = content.len() as u64;
let mut external = vk::ExternalMemoryBufferCreateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let buffer = unsafe {
device.device().create_buffer(
&vk::BufferCreateInfo::default()
.size(size)
.usage(vk::BufferUsageFlags::TRANSFER_DST)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.push_next(&mut external),
None,
)
}
.map_err(|e| anyhow!("create the exported buffer: {e:?}"))?;
let requirements = unsafe { device.device().get_buffer_memory_requirements(buffer) };
let properties = unsafe {
device
.instance()
.get_physical_device_memory_properties(device.physical_device())
};
let Some(memory_type_index) =
export_memory_type(&properties, requirements.memory_type_bits)
else {
unsafe { device.device().destroy_buffer(buffer, None) };
return Err(anyhow!("no memory type for the exported buffer"));
};
let mut export = vk::ExportMemoryAllocateInfo::default()
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().buffer(buffer);
let memory = match unsafe {
device.device().allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(memory_type_index)
.push_next(&mut export)
.push_next(&mut dedicated),
None,
)
} {
Ok(memory) => memory,
Err(e) => {
unsafe { device.device().destroy_buffer(buffer, None) };
return Err(anyhow!("allocate the exported buffer's memory: {e:?}"));
}
};
if let Err(e) = unsafe { device.device().bind_buffer_memory(buffer, memory, 0) } {
unsafe { device.device().free_memory(memory, None) };
unsafe { device.device().destroy_buffer(buffer, None) };
return Err(anyhow!("bind the exported buffer: {e:?}"));
}
let fd = (|| -> Result<OwnedFd> {
let staging = Staging::new(device, size)?;
staging.write(&[content])?;
submit_once(device, |command| {
unsafe {
device.device().cmd_copy_buffer(
command,
staging.buffer,
buffer,
&[vk::BufferCopy::default().size(size)],
);
}
})?;
let raw = unsafe {
device.external_memory_fd().get_memory_fd(
&vk::MemoryGetFdInfoKHR::default()
.memory(memory)
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT),
)
}
.map_err(|e| anyhow!("export the dma-buf fd: {e:?}"))?;
Ok(unsafe { OwnedFd::from_raw_fd(raw) })
})();
let fd = match fd {
Ok(fd) => fd,
Err(e) => {
unsafe { device.device().free_memory(memory, None) };
unsafe { device.device().destroy_buffer(buffer, None) };
return Err(e);
}
};
let mut described = Vec::with_capacity(planes.len());
for (plane, layout) in planes.iter().enumerate() {
match fd.try_clone() {
Ok(plane_fd) => {
described.push(DmaBufPlane::new(plane_fd, layout.offset, layout.stride))
}
Err(e) => {
unsafe { device.device().free_memory(memory, None) };
unsafe { device.device().destroy_buffer(buffer, None) };
return Err(anyhow!("duplicate the dma-buf fd for plane {plane}: {e}"));
}
}
}
let handle =
DmaBufHandle::new(width, height, format, DmaBufHandle::LINEAR, described, None);
Ok(Self {
device: device.device().clone(),
backing: Backing::Buffer(buffer),
memory,
handle,
})
}
fn allocate(
device: &GpuDevice,
width: u32,
height: u32,
format: SurfaceFormatKind,
tiling: Tiling,
rendered: bool,
) -> Result<Self> {
let vk_format = vk_format(format)?;
let (image, memory, handle) =
allocate_export_image(device, width, height, format, vk_format, tiling, rendered)?;
Ok(Self {
device: device.device().clone(),
backing: Backing::Image(image),
memory,
handle,
})
}
pub fn with_transfer(mut self, transfer: TransferFunction) -> Self {
self.handle.transfer = transfer;
self
}
pub fn handle(&self) -> DmaBufHandle {
self.handle.clone()
}
pub fn image(&self) -> Option<vk::Image> {
match self.backing {
Backing::Image(image) => Some(image),
Backing::Buffer(_) => None,
}
}
pub fn clear(&self, device: &GpuDevice, rgba: [f32; 4]) -> Result<()> {
let Backing::Image(image) = self.backing else {
return Err(anyhow!(
"a buffer pushed with export_at is written at export; it has no image to clear"
));
};
submit_once(device, |buffer| {
unsafe {
let to_transfer = image_barrier(
image,
vk::ImageAspectFlags::COLOR,
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::empty(),
vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::SHADER_READ,
);
device.device().cmd_pipeline_barrier(
buffer,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&[to_transfer],
);
device.device().cmd_clear_color_image(
buffer,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
&vk::ClearColorValue { float32: rgba },
&[vk::ImageSubresourceRange {
aspect_mask: vk::ImageAspectFlags::COLOR,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
}],
);
let to_general = image_barrier(
image,
vk::ImageAspectFlags::COLOR,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::GENERAL,
vk::AccessFlags::empty(),
vk::AccessFlags::COLOR_ATTACHMENT_WRITE | vk::AccessFlags::SHADER_READ,
);
device.device().cmd_pipeline_barrier(
buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::ALL_COMMANDS,
vk::DependencyFlags::empty(),
&[],
&[],
&[to_general],
);
}
})
}
pub fn fill(&self, device: &GpuDevice, planes: &[&[u8]]) -> Result<()> {
let Backing::Image(image) = self.backing else {
return Err(anyhow!(
"a buffer pushed with export_at is filled at export; it has no image to fill"
));
};
let geometry = plane_geometry(self.handle.format, self.handle.width, self.handle.height);
if geometry.is_empty() {
return Err(anyhow!(
"{:?} has no plane geometry to fill",
self.handle.format
));
}
if geometry.len() != planes.len() {
return Err(anyhow!(
"{:?} has {} planes, but {} were given",
self.handle.format,
geometry.len(),
planes.len(),
));
}
let mut offsets = Vec::with_capacity(geometry.len());
let mut total = 0u64;
for (plane, (bytes, _, _)) in geometry.iter().enumerate() {
if planes[plane].len() != *bytes {
return Err(anyhow!(
"plane {plane} is {} bytes; {:?} at {}×{} wants {bytes}",
planes[plane].len(),
self.handle.format,
self.handle.width,
self.handle.height,
));
}
offsets.push(total);
total += *bytes as u64;
}
let plane_count = geometry.len();
let drm = self.handle.modifier != DmaBufHandle::LINEAR;
let regions: Vec<vk::BufferImageCopy> = geometry
.iter()
.enumerate()
.map(|(plane, &(_, width, height))| {
vk::BufferImageCopy::default()
.buffer_offset(offsets[plane])
.buffer_row_length(width)
.buffer_image_height(height)
.image_subresource(vk::ImageSubresourceLayers {
aspect_mask: plane_aspect(plane, plane_count),
mip_level: 0,
base_array_layer: 0,
layer_count: 1,
})
.image_extent(vk::Extent3D {
width,
height,
depth: 1,
})
})
.collect();
let staging = Staging::new(device, total)?;
staging.write(planes)?;
submit_once(device, |buffer| {
unsafe {
let to_transfer: Vec<_> = (0..plane_count)
.map(|plane| {
image_barrier(
image,
subresource_aspect(drm, plane, plane_count),
vk::ImageLayout::UNDEFINED,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::AccessFlags::empty(),
vk::AccessFlags::TRANSFER_WRITE,
)
})
.collect();
device.device().cmd_pipeline_barrier(
buffer,
vk::PipelineStageFlags::TOP_OF_PIPE,
vk::PipelineStageFlags::TRANSFER,
vk::DependencyFlags::empty(),
&[],
&[],
&to_transfer,
);
device.device().cmd_copy_buffer_to_image(
buffer,
staging.buffer,
image,
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
®ions,
);
let to_general: Vec<_> = (0..plane_count)
.map(|plane| {
image_barrier(
image,
subresource_aspect(drm, plane, plane_count),
vk::ImageLayout::TRANSFER_DST_OPTIMAL,
vk::ImageLayout::GENERAL,
vk::AccessFlags::TRANSFER_WRITE,
vk::AccessFlags::SHADER_READ,
)
})
.collect();
device.device().cmd_pipeline_barrier(
buffer,
vk::PipelineStageFlags::TRANSFER,
vk::PipelineStageFlags::ALL_COMMANDS,
vk::DependencyFlags::empty(),
&[],
&[],
&to_general,
);
}
})
}
}
impl Drop for SharedBuffer {
fn drop(&mut self) {
unsafe {
match self.backing {
Backing::Image(image) => self.device.destroy_image(image, None),
Backing::Buffer(buffer) => self.device.destroy_buffer(buffer, None),
}
self.device.free_memory(self.memory, None);
}
}
}
pub struct Fence {
device: ash::Device,
external_fence_fd: ash::khr::external_fence_fd::Device,
fence: vk::Fence,
}
impl Fence {
pub fn new(device: &GpuDevice) -> Result<Self> {
let fence = unsafe {
device
.device()
.create_fence(&vk::FenceCreateInfo::default(), None)
}
.map_err(|e| anyhow!("create the fence: {e:?}"))?;
Ok(Self {
device: device.device().clone(),
external_fence_fd: ash::khr::external_fence_fd::Device::new(
device.instance(),
device.device(),
),
fence,
})
}
pub fn signal(&self, device: &GpuDevice) -> Result<()> {
unsafe {
let allocate = vk::CommandBufferAllocateInfo::default()
.command_pool(device.command_pool())
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let buffers = device
.device()
.allocate_command_buffers(&allocate)
.map_err(|e| anyhow!("allocate a command buffer: {e:?}"))?;
let buffer = buffers[0];
let result = (|| -> Result<()> {
device
.device()
.begin_command_buffer(buffer, &vk::CommandBufferBeginInfo::default())
.map_err(|e| anyhow!("begin the command buffer: {e:?}"))?;
device
.device()
.end_command_buffer(buffer)
.map_err(|e| anyhow!("end the command buffer: {e:?}"))?;
let buffers = [buffer];
let submit = vk::SubmitInfo::default().command_buffers(&buffers);
device
.device()
.queue_submit(device.queue(), &[submit], self.fence)
.map_err(|e| anyhow!("submit: {e:?}"))?;
Ok(())
})();
device
.device()
.free_command_buffers(device.command_pool(), &buffers);
result
}
}
pub fn sync_file(&self) -> Result<OwnedFd> {
let fd = unsafe {
self.external_fence_fd.get_fence_fd(
&vk::FenceGetFdInfoKHR::default()
.fence(self.fence)
.handle_type(vk::ExternalFenceHandleTypeFlags::SYNC_FD),
)
}
.map_err(|e| anyhow!("export the fence fd: {e:?}"))?;
Ok(unsafe { OwnedFd::from_raw_fd(fd) })
}
pub fn fence(&self) -> vk::Fence {
self.fence
}
}
impl Drop for Fence {
fn drop(&mut self) {
unsafe { self.device.destroy_fence(self.fence, None) };
}
}
pub(crate) fn tiled_modifier<D: ExportDevice + ?Sized>(
device: &D,
format: vk::Format,
required: vk::FormatFeatureFlags,
) -> Option<u64> {
supported_modifiers(device, format)
.into_iter()
.filter(|p| p.drm_format_modifier != DmaBufHandle::LINEAR)
.find(|p| p.drm_format_modifier_tiling_features.contains(required))
.map(|p| p.drm_format_modifier)
}
fn create_image<D: ExportDevice + ?Sized>(
device: &D,
format: vk::Format,
width: u32,
height: u32,
usage: vk::ImageUsageFlags,
modifier: Option<u64>,
) -> Result<vk::Image> {
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(format)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.usage(usage)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED)
.push_next(&mut external);
let image = match modifier {
Some(modifier) => {
let modifiers = [modifier];
let mut drm = vk::ImageDrmFormatModifierListCreateInfoEXT::default()
.drm_format_modifiers(&modifiers);
let info = info
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
.push_next(&mut drm);
unsafe { device.device().create_image(&info, None) }
}
None => {
let info = info.tiling(vk::ImageTiling::LINEAR);
unsafe { device.device().create_image(&info, None) }
}
};
image.map_err(|e| anyhow!("create the image: {e:?}"))
}
fn submit_once(device: &GpuDevice, record: impl FnOnce(vk::CommandBuffer)) -> Result<()> {
unsafe {
let allocate = vk::CommandBufferAllocateInfo::default()
.command_pool(device.command_pool())
.level(vk::CommandBufferLevel::PRIMARY)
.command_buffer_count(1);
let buffers = device
.device()
.allocate_command_buffers(&allocate)
.map_err(|error| anyhow!("allocate a command buffer: {error:?}"))?;
let buffer = buffers[0];
let result = (|| -> Result<()> {
device
.device()
.begin_command_buffer(buffer, &vk::CommandBufferBeginInfo::default())
.map_err(|error| anyhow!("begin the command buffer: {error:?}"))?;
record(buffer);
device
.device()
.end_command_buffer(buffer)
.map_err(|error| anyhow!("end the command buffer: {error:?}"))?;
let buffers = [buffer];
let submit = vk::SubmitInfo::default().command_buffers(&buffers);
let fence = device
.device()
.create_fence(&vk::FenceCreateInfo::default(), None)
.map_err(|error| anyhow!("create the submission's fence: {error:?}"))?;
device
.device()
.queue_submit(device.queue(), &[submit], fence)
.map_err(|error| anyhow!("submit: {error:?}"))?;
device
.device()
.wait_for_fences(&[fence], true, u64::MAX)
.map_err(|error| anyhow!("wait for the submission: {error:?}"))?;
device.device().destroy_fence(fence, None);
Ok(())
})();
device
.device()
.free_command_buffers(device.command_pool(), &buffers);
result
}
}
struct Staging {
device: ash::Device,
buffer: vk::Buffer,
memory: vk::DeviceMemory,
size: u64,
}
impl Staging {
fn new(device: &GpuDevice, size: u64) -> Result<Self> {
unsafe {
let buffer = device
.device()
.create_buffer(
&vk::BufferCreateInfo::default()
.size(size)
.usage(vk::BufferUsageFlags::TRANSFER_SRC)
.sharing_mode(vk::SharingMode::EXCLUSIVE),
None,
)
.map_err(|error| anyhow!("create the staging buffer: {error:?}"))?;
let requirements = device.device().get_buffer_memory_requirements(buffer);
let memory = match device.memory_type(
requirements.memory_type_bits,
vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT,
) {
Ok(memory_type_index) => device
.device()
.allocate_memory(
&vk::MemoryAllocateInfo::default()
.allocation_size(requirements.size)
.memory_type_index(memory_type_index),
None,
)
.map_err(|error| anyhow!("allocate the staging memory: {error:?}"))?,
Err(error) => {
device.device().destroy_buffer(buffer, None);
return Err(error);
}
};
if let Err(error) = device.device().bind_buffer_memory(buffer, memory, 0) {
device.device().free_memory(memory, None);
device.device().destroy_buffer(buffer, None);
return Err(anyhow!("bind the staging memory: {error:?}"));
}
Ok(Self {
device: device.device().clone(),
buffer,
memory,
size,
})
}
}
fn write(&self, planes: &[&[u8]]) -> Result<()> {
unsafe {
let mapped = self
.device
.map_memory(self.memory, 0, self.size, vk::MemoryMapFlags::empty())
.map_err(|error| anyhow!("map the staging memory: {error:?}"))?
as *mut u8;
let mut offset = 0usize;
for plane in planes {
std::ptr::copy_nonoverlapping(plane.as_ptr(), mapped.add(offset), plane.len());
offset += plane.len();
}
self.device.unmap_memory(self.memory);
}
Ok(())
}
}
impl Drop for Staging {
fn drop(&mut self) {
unsafe {
self.device.destroy_buffer(self.buffer, None);
self.device.free_memory(self.memory, None);
}
}
}
fn image_barrier(
image: vk::Image,
aspect: vk::ImageAspectFlags,
from: vk::ImageLayout,
to: vk::ImageLayout,
src_access: vk::AccessFlags,
dst_access: vk::AccessFlags,
) -> vk::ImageMemoryBarrier<'static> {
vk::ImageMemoryBarrier::default()
.old_layout(from)
.new_layout(to)
.src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
.image(image)
.subresource_range(vk::ImageSubresourceRange {
aspect_mask: aspect,
base_mip_level: 0,
level_count: 1,
base_array_layer: 0,
layer_count: 1,
})
.src_access_mask(src_access)
.dst_access_mask(dst_access)
}
fn create_instance(entry: &ash::Entry) -> Result<ash::Instance> {
let app = vk::ApplicationInfo::default().api_version(vk::make_api_version(0, 1, 1, 0));
let available = unsafe { entry.enumerate_instance_extension_properties(None) }
.map_err(|e| anyhow!("enumerate instance extensions: {e:?}"))?;
let drm = CString::new(DRM_EXTENSION).expect("a literal without a NUL");
let names: Vec<*const c_char> = available
.iter()
.filter(|e| extension_name(&e.extension_name) == DRM_EXTENSION)
.map(|_| drm.as_ptr())
.collect();
unsafe {
entry
.create_instance(
&vk::InstanceCreateInfo::default()
.application_info(&app)
.enabled_extension_names(&names),
None,
)
.map_err(|e| anyhow!("create the Vulkan instance: {e:?}"))
}
}
fn create_device(
instance: &ash::Instance,
physical_device: vk::PhysicalDevice,
queue_family_index: u32,
) -> Result<ash::Device> {
let extensions: Vec<CString> = DEVICE_EXTENSIONS
.iter()
.map(|e| CString::new(*e).expect("a literal without a NUL"))
.collect();
let extension_ptrs: Vec<*const c_char> = extensions.iter().map(|e| e.as_ptr()).collect();
let priorities = [1.0f32];
let queue_info = vk::DeviceQueueCreateInfo::default()
.queue_family_index(queue_family_index)
.queue_priorities(&priorities);
unsafe {
instance.create_device(
physical_device,
&vk::DeviceCreateInfo::default()
.queue_create_infos(std::slice::from_ref(&queue_info))
.enabled_extension_names(&extension_ptrs),
None,
)
}
.map_err(|e| anyhow!("create the Vulkan device: {e:?}"))
}
fn ensure_extensions(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> Result<()> {
let properties = unsafe { instance.enumerate_device_extension_properties(physical_device) }
.map_err(|e| anyhow!("enumerate device extensions: {e:?}"))?;
for wanted in DEVICE_EXTENSIONS {
if !properties
.iter()
.any(|p| extension_name(&p.extension_name) == wanted)
{
return Err(anyhow!("the renderer's device does not support {wanted}"));
}
}
Ok(())
}
fn render_family(instance: &ash::Instance, physical_device: vk::PhysicalDevice) -> Option<u32> {
let families = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
families
.iter()
.position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))
.map(|index| index as u32)
}
fn pick_device(instance: &ash::Instance) -> Option<(vk::PhysicalDevice, u32)> {
let physical_devices = unsafe { instance.enumerate_physical_devices() }.ok()?;
for physical_device in physical_devices {
let properties =
unsafe { instance.enumerate_device_extension_properties(physical_device) }.ok()?;
let exports = DEVICE_EXTENSIONS.iter().all(|wanted| {
properties
.iter()
.any(|p| extension_name(&p.extension_name) == *wanted)
});
if !exports {
continue;
}
let families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
let Some(index) = families
.iter()
.position(|f| f.queue_flags.contains(vk::QueueFlags::GRAPHICS))
else {
continue;
};
return Some((physical_device, index as u32));
}
None
}
fn supported_modifiers<D: ExportDevice + ?Sized>(
device: &D,
format: vk::Format,
) -> Vec<vk::DrmFormatModifierPropertiesEXT> {
unsafe {
let mut list = vk::DrmFormatModifierPropertiesListEXT::default();
let mut properties = vk::FormatProperties2::default().push_next(&mut list);
device.instance().get_physical_device_format_properties2(
device.physical_device(),
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);
device.instance().get_physical_device_format_properties2(
device.physical_device(),
format,
&mut properties,
);
modifiers
}
}
fn format_properties<D: ExportDevice + ?Sized>(
device: &D,
format: vk::Format,
) -> vk::FormatProperties {
unsafe {
device
.instance()
.get_physical_device_format_properties(device.physical_device(), format)
}
}
fn ensure_image_supported<D: ExportDevice + ?Sized>(
device: &D,
format: vk::Format,
width: u32,
height: u32,
usage: vk::ImageUsageFlags,
required: vk::FormatFeatureFlags,
modifier: Option<u64>,
) -> Result<()> {
let tiling = match modifier {
Some(_) => vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT,
None => vk::ImageTiling::LINEAR,
};
let features = match modifier {
None => format_properties(device, format).linear_tiling_features,
Some(modifier) => supported_modifiers(device, format)
.into_iter()
.find(|properties| properties.drm_format_modifier == modifier)
.map(|properties| properties.drm_format_modifier_tiling_features)
.ok_or_else(|| {
anyhow!("the device lists no DRM modifier {modifier:#x} for {format:?}")
})?,
};
if !features.contains(required) {
return Err(anyhow!(
"the device cannot make a {tiling:?} {format:?} image: it needs {required:?}, and the \
format carries {features:?}"
));
}
let max_extent = unsafe {
let mut drm = vk::PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
.drm_format_modifier(modifier.unwrap_or(0));
let mut info = vk::PhysicalDeviceImageFormatInfo2::default()
.format(format)
.ty(vk::ImageType::TYPE_2D)
.tiling(tiling)
.usage(usage);
if modifier.is_some() {
info = info.push_next(&mut drm);
}
let mut properties = vk::ImageFormatProperties2::default();
device
.instance()
.get_physical_device_image_format_properties2(
device.physical_device(),
&info,
&mut properties,
)
.map_err(|error| {
anyhow!("ask the device for its {tiling:?} {format:?} limits: {error:?}")
})?;
properties.image_format_properties.max_extent
};
if width > max_extent.width || height > max_extent.height {
return Err(anyhow!(
"the device's {format:?} images cap at {}×{}, asked for {width}×{height}",
max_extent.width,
max_extent.height,
));
}
Ok(())
}
fn vk_format(format: SurfaceFormatKind) -> Result<vk::Format> {
match format {
SurfaceFormatKind::Bgra8(_) => Ok(vk::Format::B8G8R8A8_UNORM),
SurfaceFormatKind::Rgba8(_) => Ok(vk::Format::R8G8B8A8_UNORM),
SurfaceFormatKind::Nv12(_) => Ok(vk::Format::G8_B8R8_2PLANE_420_UNORM),
other => Err(anyhow!(
"{other:?} has no Vulkan format in the dma-buf hatch yet"
)),
}
}
fn usage_for(rendered: bool) -> vk::ImageUsageFlags {
let base = vk::ImageUsageFlags::SAMPLED
| vk::ImageUsageFlags::TRANSFER_SRC
| vk::ImageUsageFlags::TRANSFER_DST;
if rendered {
base | vk::ImageUsageFlags::COLOR_ATTACHMENT
} else {
base
}
}
fn required_features(usage: vk::ImageUsageFlags) -> vk::FormatFeatureFlags {
let mut required = vk::FormatFeatureFlags::empty();
if usage.contains(vk::ImageUsageFlags::SAMPLED) {
required |= vk::FormatFeatureFlags::SAMPLED_IMAGE;
}
if usage.contains(vk::ImageUsageFlags::COLOR_ATTACHMENT) {
required |= vk::FormatFeatureFlags::COLOR_ATTACHMENT;
}
required
}
fn plane_geometry(format: SurfaceFormatKind, width: u32, height: u32) -> Vec<(usize, u32, u32)> {
match format {
SurfaceFormatKind::Bgra8(_) | SurfaceFormatKind::Rgba8(_) => {
vec![((width as usize * height as usize) * 4, width, height)]
}
SurfaceFormatKind::Nv12(_) => vec![
(width as usize * height as usize, width, height),
(
(width as usize / 2) * (height as usize / 2) * 2,
width / 2,
height / 2,
),
],
_ => Vec::new(),
}
}
fn memory_type_index(
properties: &vk::PhysicalDeviceMemoryProperties,
bits: u32,
wanted: impl Fn(vk::MemoryPropertyFlags) -> bool,
) -> Option<u32> {
(0..properties.memory_type_count).find(|index| {
bits & (1u32 << index) != 0 && wanted(properties.memory_types[*index as usize].property_flags)
})
}
fn export_memory_type(properties: &vk::PhysicalDeviceMemoryProperties, bits: u32) -> Option<u32> {
memory_type_index(properties, bits, |flags| {
flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL)
&& !flags.contains(vk::MemoryPropertyFlags::HOST_VISIBLE)
})
.or_else(|| {
memory_type_index(properties, bits, |flags| {
flags.contains(vk::MemoryPropertyFlags::DEVICE_LOCAL)
})
})
.or_else(|| memory_type_index(properties, bits, |_| true))
}
fn declared_span(geometry: &[(usize, u32, u32)], planes: &[PlaneLayout]) -> Result<u64> {
let mut span = 0u64;
for (plane, ((bytes, plane_width, plane_height), layout)) in
geometry.iter().zip(planes).enumerate()
{
let row_bytes = (*bytes / *plane_height as usize) as u64;
anyhow::ensure!(
u64::from(layout.stride) >= row_bytes,
"plane {plane} declares a stride of {}, but a {plane_width}-wide row needs {row_bytes}",
layout.stride,
);
span = span.max(
layout.offset + u64::from(layout.stride) * (u64::from(*plane_height) - 1) + row_bytes,
);
}
Ok(span)
}
fn check_plane_layouts(
geometry: &[(usize, u32, u32)],
planes: &[PlaneLayout],
content: usize,
) -> Result<()> {
let span = declared_span(geometry, planes)?;
anyhow::ensure!(
span <= content as u64,
"the declared plane layout spans {span} bytes, past the {content} bytes of content given",
);
Ok(())
}
fn plane_aspect(plane: usize, plane_count: usize) -> vk::ImageAspectFlags {
if plane_count == 1 {
vk::ImageAspectFlags::COLOR
} else if plane == 0 {
vk::ImageAspectFlags::PLANE_0
} else {
vk::ImageAspectFlags::PLANE_1
}
}
fn subresource_aspect(drm: bool, plane: usize, plane_count: usize) -> vk::ImageAspectFlags {
if drm {
if plane == 0 {
vk::ImageAspectFlags::MEMORY_PLANE_0_EXT
} else {
vk::ImageAspectFlags::MEMORY_PLANE_1_EXT
}
} else if plane_count == 1 {
vk::ImageAspectFlags::COLOR
} else if plane == 0 {
vk::ImageAspectFlags::PLANE_0
} else {
vk::ImageAspectFlags::PLANE_1
}
}
fn extension_name(raw: &[c_char]) -> &str {
unsafe { CStr::from_ptr(raw.as_ptr()) }
.to_str()
.unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
fn probe_device() -> Option<GpuDevice> {
let _loading = LOADER
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
let entry = unsafe { ash::Entry::load() }.ok()?;
let instance = create_instance(&entry).ok()?;
let wanted = std::env::var("ZED_DEVICE_ID")
.ok()
.and_then(|value| u32::from_str_radix(value.trim_start_matches("0x"), 16).ok());
let devices = unsafe { instance.enumerate_physical_devices() }.ok()?;
let chosen = devices.iter().copied().find(|physical| {
let properties = unsafe { instance.get_physical_device_properties(*physical) };
wanted.is_none_or(|id| properties.device_id == id)
})?;
let family = render_family(&instance, chosen)?;
ensure_extensions(&instance, chosen).ok()?;
GpuDevice::assemble(Some(entry), instance, true, false, chosen, family).ok()
}
enum LinearNv12<'a> {
Native,
Declared(&'a [vk::SubresourceLayout]),
}
fn reported_layout(
device: &GpuDevice,
width: u32,
height: u32,
how: LinearNv12<'_>,
) -> Result<(vk::SubresourceLayout, vk::SubresourceLayout, u64), vk::Result> {
let drm = matches!(how, LinearNv12::Declared(_));
let mut explicit = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default()
.drm_format_modifier(DmaBufHandle::LINEAR);
if let LinearNv12::Declared(layouts) = how {
explicit = explicit.plane_layouts(layouts);
}
let mut info = vk::ImageCreateInfo::default()
.image_type(vk::ImageType::TYPE_2D)
.format(vk::Format::G8_B8R8_2PLANE_420_UNORM)
.extent(vk::Extent3D {
width,
height,
depth: 1,
})
.mip_levels(1)
.array_layers(1)
.samples(vk::SampleCountFlags::TYPE_1)
.tiling(if drm {
vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT
} else {
vk::ImageTiling::LINEAR
})
.usage(vk::ImageUsageFlags::SAMPLED)
.sharing_mode(vk::SharingMode::EXCLUSIVE)
.initial_layout(vk::ImageLayout::UNDEFINED);
if drm {
info = info.push_next(&mut explicit);
}
let image = unsafe { device.device().create_image(&info, None) }?;
let plane = |index: usize| unsafe {
device.device().get_image_subresource_layout(
image,
vk::ImageSubresource {
aspect_mask: subresource_aspect(drm, index, 2),
mip_level: 0,
array_layer: 0,
},
)
};
let luma = plane(0);
let chroma = plane(1);
let size = unsafe { device.device().get_image_memory_requirements(image) }.size;
unsafe { device.device().destroy_image(image, None) };
Ok((luma, chroma, size))
}
#[test]
fn the_linear_layout_report_matches_the_device() {
let Some(device) = probe_device() else {
eprintln!("skipping: no Vulkan device for the layout report");
return;
};
let (width, height) = (64u32, 64u32);
let luma_bytes = u64::from(width) * u64::from(height);
let chroma_bytes = u64::from(width / 2) * u64::from(height / 2) * 2;
let span = luma_bytes + chroma_bytes;
let planes = [
PlaneLayout {
offset: 0,
stride: width,
},
PlaneLayout {
offset: luma_bytes,
stride: width,
},
];
let (native_luma, native_chroma, native_memory) =
match reported_layout(&device, width, height, LinearNv12::Native) {
Ok(measured) => measured,
Err(error) => {
eprintln!("skipping: no plain linear NV12 image ({error:?})");
return;
}
};
eprintln!(
"native linear NV12: luma {}@{}, chroma {}@{}, memory {native_memory}",
native_luma.row_pitch,
native_luma.offset,
native_chroma.row_pitch,
native_chroma.offset,
);
let report = match device.linear_layout(SurfaceFormatKind::nv12(), width, height, &planes) {
Ok(report) => report,
Err(error) => {
eprintln!("the device will not make the declared layout: {error:#}");
return;
}
};
assert_eq!(
report.native,
vec![
PlaneLayout {
offset: native_luma.offset,
stride: native_luma.row_pitch as u32,
},
PlaneLayout {
offset: native_chroma.offset,
stride: native_chroma.row_pitch as u32,
},
],
"the report's native layout must be the plain image's own",
);
let native_layouts = [
vk::SubresourceLayout::default()
.offset(native_luma.offset)
.size(luma_bytes)
.row_pitch(native_luma.row_pitch),
vk::SubresourceLayout::default()
.offset(native_chroma.offset)
.size(chroma_bytes)
.row_pitch(native_chroma.row_pitch),
];
let measured =
match reported_layout(&device, width, height, LinearNv12::Declared(&native_layouts)) {
Ok((_, _, size)) => size,
Err(error) => {
eprintln!("the device refused its own layout when asked directly ({error:?})");
return;
}
};
eprintln!(
"reported linear NV12: native {:?}, required {}, declared {} (measured requirement \
{measured})",
report.native, report.required, report.declared,
);
assert_eq!(
report.declared, span,
"the report's declared span is the layout the producer proposed",
);
assert_eq!(
report.required, measured,
"the report's requirement must be the device's own, measured independently",
);
assert!(
report.required >= report.declared,
"an image never needs less than its own layout spans",
);
assert_eq!(
report.backed(),
report.required == report.declared,
"a layout is backed exactly when the device needs no more than it spans",
);
}
#[test]
fn a_linear_buffer_exports_a_dma_buf() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::bgra8(), Tiling::Linear)
.expect("allocate a shareable buffer");
let handle = buffer.handle();
assert_eq!(handle.modifier, DmaBufHandle::LINEAR, "asked for linear");
assert_eq!(handle.planes.len(), 1, "a single-plane format");
assert!(
handle.planes[0].stride >= 64 * 4,
"a BGRA8 row is at least four bytes a pixel"
);
assert_eq!(handle.width, 64);
assert_eq!(handle.height, 64);
}
#[test]
fn a_shared_buffer_can_declare_linear_light() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 4, 4, SurfaceFormatKind::rgba8(), Tiling::Linear)
.expect("allocate a shareable buffer");
assert_eq!(buffer.handle().transfer, TransferFunction::Srgb);
let linear = buffer.with_transfer(TransferFunction::Linear);
assert_eq!(linear.handle().transfer, TransferFunction::Linear);
}
#[test]
fn a_gpu_reports_the_render_node_it_backs() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let id = device.gpu_id();
eprintln!("gpu: {} render node {:?}", id.name, id.render_node);
assert!(!id.name.is_empty(), "a Vulkan device reports a name");
if let Some(node) = &id.render_node {
assert!(
node.to_string_lossy().contains("renderD"),
"a DRM render node names a render node: {node:?}"
);
}
}
#[test]
fn a_producers_own_device_refuses_tiling() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::bgra8(), Tiling::Tiled)
.expect("allocate a shareable buffer");
let handle = buffer.handle();
assert_eq!(
handle.modifier,
DmaBufHandle::LINEAR,
"a device the producer opened itself must not hand out a modifier"
);
assert_eq!(handle.planes.len(), 1, "a single-plane format");
assert!(
handle.planes[0].stride >= 64 * 4,
"a linear row is at least four bytes a pixel"
);
}
#[test]
fn a_two_plane_nv12_buffer_exports_two_planes() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::nv12(), Tiling::Linear)
.expect("allocate a shareable buffer");
let handle = buffer.handle();
assert_eq!(handle.planes.len(), 2, "NV12 is two planes");
assert!(
handle.planes[0].stride >= 64,
"a luma row is at least a byte a pixel"
);
assert!(
handle.planes[1].stride >= 32,
"a chroma row is at least a byte every two pixels"
);
assert!(
handle.planes[1].offset >= handle.planes[0].stride as u64 * 64,
"the chroma plane follows the luma plane"
);
eprintln!(
"nv12: luma offset {} stride {}, chroma offset {} stride {}",
handle.planes[0].offset,
handle.planes[0].stride,
handle.planes[1].offset,
handle.planes[1].stride,
);
}
#[test]
fn a_pushed_buffer_exports_at_the_layout_it_declares() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let luma = 64usize * 64;
let chroma_offset = luma + 4096;
let content = vec![0u8; chroma_offset + 32 * 32 * 2];
let buffer = SharedBuffer::export_at(
&device,
64,
64,
SurfaceFormatKind::nv12(),
&[
PlaneLayout {
offset: 0,
stride: 64,
},
PlaneLayout {
offset: chroma_offset as u64,
stride: 64,
},
],
&content,
)
.expect("export at a declared layout");
let handle = buffer.handle();
assert_eq!(handle.modifier, DmaBufHandle::LINEAR, "a pushed buffer is linear");
assert_eq!(handle.planes[0].offset, 0);
assert_eq!(
handle.planes[1].offset, chroma_offset as u64,
"the declared chroma offset, not the driver's",
);
assert_eq!(handle.planes[1].stride, 64);
assert!(buffer.image().is_none(), "a pushed buffer backs no image");
}
#[test]
fn a_pushed_buffer_refuses_a_layout_that_runs_past_its_content() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let content = vec![0u8; 64 * 64];
let refused = SharedBuffer::export_at(
&device,
64,
64,
SurfaceFormatKind::nv12(),
&[
PlaneLayout {
offset: 0,
stride: 64,
},
PlaneLayout {
offset: 4096,
stride: 64,
},
],
&content,
);
assert!(
refused.is_err(),
"a chroma plane past the content is refused",
);
}
#[test]
fn a_fence_exports_a_sync_file() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let fence = Fence::new(&device).expect("create a fence");
fence.signal(&device).expect("signal the fence");
let sync_file = fence.sync_file().expect("export a sync_file");
assert!(
std::os::fd::AsRawFd::as_raw_fd(&sync_file) >= 0,
"a sync_file is a valid descriptor"
);
}
#[test]
fn a_cleared_buffer_is_ready_to_hand_over() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::bgra8(), Tiling::Linear)
.expect("allocate a shareable buffer");
buffer
.clear(&device, [0.5, 0.25, 0.125, 1.0])
.expect("clear the buffer");
assert_eq!(buffer.handle().planes.len(), 1);
}
#[test]
fn a_filled_nv12_buffer_is_ready_to_hand_over() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::nv12(), Tiling::Linear)
.expect("allocate a shareable buffer");
let luma = vec![128u8; 64 * 64];
let mut chroma = vec![0u8; 32 * 32 * 2];
for pair in chroma.as_chunks_mut::<2>().0 {
pair[0] = 90;
pair[1] = 120;
}
buffer
.fill(&device, &[&luma, &chroma])
.expect("fill the two planes");
assert_eq!(buffer.handle().planes.len(), 2, "NV12 stays two planes");
}
#[test]
fn a_fill_of_the_wrong_size_is_refused() {
let Ok(device) = GpuDevice::open() else {
eprintln!("skipping: no Vulkan device exports a dma-buf");
return;
};
let buffer = SharedBuffer::new(&device, 64, 64, SurfaceFormatKind::nv12(), Tiling::Linear)
.expect("allocate a shareable buffer");
let luma = vec![0u8; 64 * 64];
let short_chroma = vec![0u8; 16];
let error = buffer
.fill(&device, &[&luma, &short_chroma])
.expect_err("a chroma plane of the wrong length is refused");
assert!(
error.to_string().contains("plane 1"),
"the error should name the plane: {error}"
);
}
#[cfg(feature = "gpui")]
#[test]
fn from_adapter_reads_the_sessions_render_node() {
let info = gpui::GpuAdapterInfo::new(
gpui::GpuId {
permanent: gpui::PermanentGpuId::Pci {
vendor_id: Some(0x8086),
device_id: 0x1916,
subsys_id: None,
revision: None,
},
session: Some(gpui::SessionGpuId::Drm { render_minor: 128 }),
},
"Intel(R) HD Graphics 520",
gpui::GpuDeviceType::Integrated,
gpui::GpuBackend::Vulkan,
false,
);
let gpu = GpuId::from_adapter(&info).expect("a DRM session names a render node");
assert_eq!(gpu.name, "Intel(R) HD Graphics 520");
assert_eq!(gpu.render_node, Some(PathBuf::from("/dev/dri/renderD128")));
}
#[cfg(feature = "gpui")]
#[test]
fn from_adapter_refuses_an_adapter_it_cannot_name() {
let info = gpui::GpuAdapterInfo::new(
gpui::GpuId {
permanent: gpui::PermanentGpuId::Name("llvmpipe".into()),
session: None,
},
"llvmpipe",
gpui::GpuDeviceType::Cpu,
gpui::GpuBackend::Vulkan,
true,
);
assert!(GpuId::from_adapter(&info).is_none());
}
}