use std::collections::HashMap;
use std::os::fd::BorrowedFd;
use drm::buffer::{DrmFourcc, Handle as BufferHandle, PlanarBuffer};
use drm::control::{Device as ControlDevice, crtc, framebuffer, plane};
use denise::Rect;
use denise_drm::Card;
use crate::VideoError;
use crate::decode::{DecodedFrame, Decoder};
use crate::v4l2;
struct Imported {
size: (u32, u32),
format: DrmFourcc,
pitches: [u32; 4],
handles: [Option<BufferHandle>; 4],
offsets: [u32; 4],
}
impl PlanarBuffer for Imported {
fn size(&self) -> (u32, u32) {
self.size
}
fn format(&self) -> DrmFourcc {
self.format
}
fn modifier(&self) -> Option<drm::buffer::DrmModifier> {
None
}
fn pitches(&self) -> [u32; 4] {
self.pitches
}
fn handles(&self) -> [Option<BufferHandle>; 4] {
self.handles
}
fn offsets(&self) -> [u32; 4] {
self.offsets
}
}
pub struct VideoPlane {
plane: plane::Handle,
crtc: crtc::Handle,
framebuffers: HashMap<u32, framebuffer::Handle>,
dst: Rect,
shown: bool,
}
impl VideoPlane {
pub fn new(card: &Card, crtc: crtc::Handle, dst: Rect) -> Result<Self, VideoError> {
let resources = card
.resource_handles()
.map_err(|e| VideoError::drm("resources", e))?;
let planes = card
.plane_handles()
.map_err(|e| VideoError::drm("planes", e))?;
for handle in planes {
let Ok(info) = card.get_plane(handle) else {
continue;
};
if !resources
.filter_crtcs(info.possible_crtcs())
.contains(&crtc)
{
continue;
}
if info.crtc().is_some() || info.framebuffer().is_some() {
continue;
}
let formats = info.formats();
let takes = |f: DrmFourcc| formats.contains(&(f as u32));
if takes(DrmFourcc::Nv12) || takes(DrmFourcc::Yuv420) {
return Ok(Self {
plane: handle,
crtc,
framebuffers: HashMap::new(),
dst,
shown: false,
});
}
}
Err(VideoError::NoPlane)
}
pub fn set_dst(&mut self, dst: Rect) {
self.dst = dst;
}
pub fn show(
&mut self,
card: &Card,
decoder: &Decoder,
frame: &DecodedFrame,
) -> Result<(), VideoError> {
let fb = match self.framebuffers.get(&frame.index) {
Some(&fb) => fb,
None => {
let dmabuf = decoder.dmabuf(frame.index).ok_or(VideoError::NoFrames)?;
let fb = import(card, dmabuf, frame)?;
self.framebuffers.insert(frame.index, fb);
fb
}
};
card.set_plane(
self.plane,
self.crtc,
Some(fb),
0,
(
self.dst.x,
self.dst.y,
self.dst.width.max(0) as u32,
self.dst.height.max(0) as u32,
),
(0, 0, frame.width << 16, frame.height << 16),
)
.map_err(|e| VideoError::drm("set_plane", e))?;
self.shown = true;
Ok(())
}
pub fn hide(&mut self, card: &Card) -> Result<(), VideoError> {
if !self.shown {
return Ok(());
}
card.set_plane(self.plane, self.crtc, None, 0, (0, 0, 0, 0), (0, 0, 0, 0))
.map_err(|e| VideoError::drm("set_plane off", e))?;
self.shown = false;
Ok(())
}
}
fn import(
card: &Card,
dmabuf: BorrowedFd<'_>,
frame: &DecodedFrame,
) -> Result<framebuffer::Handle, VideoError> {
let handle = card
.prime_fd_to_buffer(dmabuf)
.map_err(|e| VideoError::drm("prime import", e))?;
let luma = frame.stride * frame.height;
let imported = match frame.pixelformat {
v4l2::PIX_FMT_NV12 => Imported {
size: (frame.width, frame.height),
format: DrmFourcc::Nv12,
pitches: [frame.stride, frame.stride, 0, 0],
handles: [Some(handle), Some(handle), None, None],
offsets: [0, luma, 0, 0],
},
v4l2::PIX_FMT_YUV420 => Imported {
size: (frame.width, frame.height),
format: DrmFourcc::Yuv420,
pitches: [frame.stride, frame.stride / 2, frame.stride / 2, 0],
handles: [Some(handle), Some(handle), Some(handle), None],
offsets: [0, luma, luma + luma / 4, 0],
},
other => return Err(VideoError::UnsupportedFormat(other)),
};
card.add_planar_framebuffer(&imported, drm::control::FbCmd2Flags::empty())
.map_err(|e| VideoError::drm("addfb2", e))
}