use std::io::{Seek, SeekFrom};
use std::os::fd::{AsRawFd, OwnedFd};
use std::sync::Arc;
use crate::{
Color, Display, DrmPrimeSurfaceDescriptor, ExternalBufferDescriptor, MemoryType, Surface, UsageHint,
VADRMPRIMESurfaceDescriptor, VA_FOURCC_ARGB, VA_FOURCC_BGRA, VA_FOURCC_BGRX, VA_FOURCC_I420, VA_FOURCC_NV12,
VA_FOURCC_P010, VA_FOURCC_RGBA, VA_FOURCC_RGBX, VA_FOURCC_XRGB, VA_FOURCC_YUY2, VA_FOURCC_YV12, VA_RT_FORMAT_RGB32,
VA_RT_FORMAT_YUV420, VA_RT_FORMAT_YUV420_10, VA_RT_FORMAT_YUV422,
};
pub mod drm {
const fn fourcc(code: &[u8; 4]) -> u32 {
u32::from_le_bytes(*code)
}
pub const NV12: u32 = fourcc(b"NV12");
pub const P010: u32 = fourcc(b"P010");
pub const YUV420: u32 = fourcc(b"YU12");
pub const YVU420: u32 = fourcc(b"YV12");
pub const YUYV: u32 = fourcc(b"YUYV");
pub const XRGB8888: u32 = fourcc(b"XR24");
pub const ARGB8888: u32 = fourcc(b"AR24");
pub const XBGR8888: u32 = fourcc(b"XB24");
pub const ABGR8888: u32 = fourcc(b"AB24");
pub const BGRX8888: u32 = fourcc(b"BX24");
pub const BGRA8888: u32 = fourcc(b"BA24");
}
pub fn va_fourcc(drm_format: u32) -> Option<u32> {
Some(match drm_format {
drm::NV12 => VA_FOURCC_NV12,
drm::P010 => VA_FOURCC_P010,
drm::YUV420 => VA_FOURCC_I420,
drm::YVU420 => VA_FOURCC_YV12,
drm::YUYV => VA_FOURCC_YUY2,
drm::XRGB8888 => VA_FOURCC_BGRX,
drm::ARGB8888 => VA_FOURCC_BGRA,
drm::XBGR8888 => VA_FOURCC_RGBX,
drm::ABGR8888 => VA_FOURCC_RGBA,
drm::BGRX8888 => VA_FOURCC_XRGB,
drm::BGRA8888 => VA_FOURCC_ARGB,
_ => return None,
})
}
pub fn rt_format(fourcc: u32) -> Option<u32> {
match fourcc {
VA_FOURCC_NV12 | VA_FOURCC_I420 | VA_FOURCC_YV12 => Some(VA_RT_FORMAT_YUV420),
VA_FOURCC_P010 => Some(VA_RT_FORMAT_YUV420_10),
VA_FOURCC_YUY2 => Some(VA_RT_FORMAT_YUV422),
VA_FOURCC_BGRA | VA_FOURCC_BGRX | VA_FOURCC_RGBA | VA_FOURCC_RGBX | VA_FOURCC_ARGB | VA_FOURCC_XRGB => {
Some(VA_RT_FORMAT_RGB32)
}
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Plane {
pub offset: u32,
pub pitch: u32,
}
#[derive(Debug)]
pub struct DmaBuf {
pub drm_format: u32,
pub modifier: u64,
pub width: u32,
pub height: u32,
pub planes: Vec<Plane>,
pub fd: OwnedFd,
pub color: Option<Color>,
}
impl DmaBuf {
pub fn from_prime(mut exported: DrmPrimeSurfaceDescriptor) -> anyhow::Result<Self> {
if exported.objects.len() != 1 || exported.layers.len() != 1 {
anyhow::bail!(
"expected one object and one layer, the export has {} and {}",
exported.objects.len(),
exported.layers.len()
);
}
let object = exported.objects.remove(0);
let layer = &exported.layers[0];
Ok(Self {
drm_format: layer.drm_format,
modifier: object.drm_format_modifier,
width: exported.width,
height: exported.height,
planes: (0..(layer.num_planes as usize).min(4))
.map(|i| Plane {
offset: layer.offset[i],
pitch: layer.pitch[i],
})
.collect(),
fd: object.fd,
color: None,
})
}
pub fn fourcc(&self) -> Option<u32> {
va_fourcc(self.drm_format)
}
pub fn import(self, display: &Arc<Display>, usage_hint: Option<UsageHint>) -> anyhow::Result<Surface<DmaBuf>> {
let fourcc = self
.fourcc()
.ok_or_else(|| anyhow::anyhow!("no VA-API format for DRM format {:#010x}", self.drm_format))?;
let rt_format =
rt_format(fourcc).ok_or_else(|| anyhow::anyhow!("no VA-API render target format for {fourcc:#010x}"))?;
if self.planes.is_empty() || self.planes.len() > 4 {
anyhow::bail!("a DMA-BUF import takes one to four planes, got {}", self.planes.len());
}
let (width, height) = (self.width, self.height);
display
.create_surfaces(rt_format, Some(fourcc), width, height, usage_hint, vec![self])
.map_err(|e| anyhow::anyhow!("import a {width}x{height} DMA-BUF as a surface: {e:?}"))?
.pop()
.ok_or_else(|| anyhow::anyhow!("vaCreateSurfaces returned no surface"))
}
}
impl ExternalBufferDescriptor for DmaBuf {
const MEMORY_TYPE: MemoryType = MemoryType::DrmPrime2;
type DescriptorAttribute = VADRMPRIMESurfaceDescriptor;
fn va_surface_attribute(&mut self) -> Self::DescriptorAttribute {
let mut descriptor = VADRMPRIMESurfaceDescriptor {
fourcc: self.fourcc().unwrap_or(0),
width: self.width,
height: self.height,
num_objects: 1,
num_layers: 1,
..Default::default()
};
descriptor.objects[0].fd = self.fd.as_raw_fd();
descriptor.objects[0].size = buffer_size(&self.fd);
descriptor.objects[0].drm_format_modifier = self.modifier;
let layer = &mut descriptor.layers[0];
layer.drm_format = self.drm_format;
layer.num_planes = self.planes.len().min(4) as u32;
for (index, plane) in self.planes.iter().take(4).enumerate() {
layer.object_index[index] = 0;
layer.offset[index] = plane.offset;
layer.pitch[index] = plane.pitch;
}
descriptor
}
}
fn buffer_size(fd: &OwnedFd) -> u32 {
let Ok(clone) = fd.try_clone() else {
return 0;
};
let mut file = std::fs::File::from(clone);
let size = file
.seek(SeekFrom::End(0))
.ok()
.and_then(|size| u32::try_from(size).ok());
let _ = file.seek(SeekFrom::Start(0));
size.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_mapped_format_has_a_render_target() {
for format in [
drm::NV12,
drm::P010,
drm::YUV420,
drm::YVU420,
drm::YUYV,
drm::XRGB8888,
drm::ARGB8888,
drm::XBGR8888,
drm::ABGR8888,
drm::BGRX8888,
drm::BGRA8888,
] {
let fourcc = va_fourcc(format).expect("mapped");
assert!(rt_format(fourcc).is_some(), "{format:#010x} has no render target");
}
}
#[test]
fn packed_rgb_maps_to_the_byte_order_name() {
assert_eq!(va_fourcc(drm::XRGB8888), Some(VA_FOURCC_BGRX));
assert_eq!(va_fourcc(drm::ABGR8888), Some(VA_FOURCC_RGBA));
assert_eq!(va_fourcc(drm::YUV420), Some(VA_FOURCC_I420));
assert_eq!(va_fourcc(u32::from_le_bytes(*b"ZZZZ")), None);
}
}