use crate::committer::Committer;
use crate::render::device::DrmCard;
use crate::render::framebuffer::Framebuffer;
use anyhow::{Context, Result, anyhow, bail};
use drm::Device as DrmDevice;
use drm::control::{
self, AtomicCommitFlags, Device as ControlDevice, atomic::AtomicModeReq, connector, crtc,
plane, property,
};
use std::rc::Rc;
use std::sync::mpsc::Receiver;
use std::sync::{Arc, mpsc};
use std::thread;
use std::thread::JoinHandle;
#[derive(Debug)]
struct AtomicProps {
conn_crtc_id: property::Handle,
crtc_mode_id: property::Handle,
crtc_active: property::Handle,
plane_fb_id: property::Handle,
plane_crtc_id: property::Handle,
plane_src_x: property::Handle,
plane_src_y: property::Handle,
plane_src_w: property::Handle,
plane_src_h: property::Handle,
plane_crtc_x: property::Handle,
plane_crtc_y: property::Handle,
plane_crtc_w: property::Handle,
plane_crtc_h: property::Handle,
}
pub struct AtomicCommitter {
pub d: Arc<DrmCard>,
pub fb: Option<Rc<Framebuffer<DrmCard>>>,
connector: connector::Info,
mode: control::Mode,
crtc: crtc::Info,
plane: plane::Handle,
props: AtomicProps,
srx: Receiver<control::Event>,
#[allow(unused)]
event_handle: JoinHandle<Result<()>>,
}
impl AtomicCommitter {
fn build_atomic_req(&self, fb: &Framebuffer<DrmCard>, mode_set: bool) -> Result<AtomicModeReq> {
let (fb_w, fb_h) = (fb.width, fb.height);
let (mode_w, mode_h) = self.mode.size();
let scale_w = mode_w as f64 / fb_w as f64;
let scale_h = mode_h as f64 / fb_h as f64;
let scale = scale_w.min(scale_h);
let crtc_w = (fb_w as f64 * scale).round() as u64;
let crtc_h = (fb_h as f64 * scale).round() as u64;
let crtc_x = ((mode_w as u64 - crtc_w) / 2) as i64;
let crtc_y = ((mode_h as u64 - crtc_h) / 2) as i64;
let mut req = AtomicModeReq::new();
req.add_property(
self.connector.handle(),
self.props.conn_crtc_id,
property::Value::CRTC(Some(self.crtc.handle())),
);
if mode_set {
let mode_blob = self.d.create_property_blob(&self.mode)?;
req.add_property(self.crtc.handle(), self.props.crtc_mode_id, mode_blob);
}
req.add_property(
self.crtc.handle(),
self.props.crtc_active,
property::Value::Boolean(true),
);
req.add_property(
self.plane,
self.props.plane_fb_id,
property::Value::Framebuffer(Some(fb.id)),
);
req.add_property(
self.plane,
self.props.plane_crtc_id,
property::Value::CRTC(Some(self.crtc.handle())),
);
req.add_property(
self.plane,
self.props.plane_src_x,
property::Value::UnsignedRange(0),
);
req.add_property(
self.plane,
self.props.plane_src_y,
property::Value::UnsignedRange(0),
);
req.add_property(
self.plane,
self.props.plane_src_w,
property::Value::UnsignedRange((fb_w as u64) << 16),
);
req.add_property(
self.plane,
self.props.plane_src_h,
property::Value::UnsignedRange((fb_h as u64) << 16),
);
req.add_property(
self.plane,
self.props.plane_crtc_x,
property::Value::SignedRange(crtc_x),
);
req.add_property(
self.plane,
self.props.plane_crtc_y,
property::Value::SignedRange(crtc_y),
);
req.add_property(
self.plane,
self.props.plane_crtc_w,
property::Value::UnsignedRange(crtc_w),
);
req.add_property(
self.plane,
self.props.plane_crtc_h,
property::Value::UnsignedRange(crtc_h),
);
Ok(req)
}
fn pick_primary_plane(
d: &DrmCard,
res: &control::ResourceHandles,
crtc: crtc::Handle,
) -> Result<plane::Handle> {
let mut compatible = Vec::new();
for plane_h in d.plane_handles()? {
let plane_info = d.get_plane(plane_h)?;
let compatible_crtcs = res.filter_crtcs(plane_info.possible_crtcs());
if !compatible_crtcs.contains(&crtc) {
continue;
}
let props = d.get_properties(plane_h)?;
let mut is_primary = false;
for (&prop_id, &val) in props.iter() {
let info = d.get_property(prop_id)?;
if info.name().to_str().map(|x| x == "type").unwrap_or(false) {
is_primary = val == control::PlaneType::Primary as u64;
break;
}
}
if is_primary {
return Ok(plane_h);
}
compatible.push(plane_h);
}
compatible
.first()
.copied()
.ok_or_else(|| anyhow!("no plane compatible with the selected CRTC"))
}
fn atomic_commit(&self, flags: AtomicCommitFlags) -> Result<()> {
if let Some(fb) = &self.fb {
let req =
self.build_atomic_req(fb, flags.contains(AtomicCommitFlags::ALLOW_MODESET))?;
self.d.atomic_commit(flags, req).context("atomic commit")?;
Ok(())
} else {
bail!("Framebuffer is in valid state")
}
}
}
impl Committer<Arc<DrmCard>, Rc<Framebuffer<DrmCard>>> for AtomicCommitter {
fn init(d: Arc<DrmCard>) -> Result<Self> {
d.enable_atomic()?;
DrmDevice::set_client_capability(d.as_ref(), drm::ClientCapability::UniversalPlanes, true)
.context("enabling DRM universal planes capability")?;
let res = d.resource_handles()?;
let con_handle = res
.connectors()
.iter()
.copied()
.find(|&conn| {
d.get_connector(conn)
.map(|info| info.state() == connector::State::Connected)
.unwrap_or(false)
})
.context("no connected DRM connector found")?;
let connector = d.get_connector(con_handle)?;
let mode = connector
.modes()
.iter()
.max_by(|a, b| {
let a_size = a.size();
let b_size = b.size();
let a_area = (a_size.0 as u64) * (a_size.1 as u64);
let b_area = (b_size.0 as u64) * (b_size.1 as u64);
a_area
.cmp(&b_area)
.then_with(|| a.vrefresh().cmp(&b.vrefresh()))
})
.copied()
.context("connector has no modes")?;
connector.modes().iter().for_each(|x| {
println!(
"Available mode : {}x{}@{}",
x.size().0,
x.size().1,
x.vrefresh()
);
});
println!(
"Selected mode : {}x{}@{}",
mode.size().0,
mode.size().1,
mode.vrefresh()
);
let encoder_handle = connector
.encoders()
.first()
.copied()
.context("connector has no encoder")?;
let encoder = d.get_encoder(encoder_handle)?;
let crtc_handle = encoder.crtc().unwrap_or_else(|| res.crtcs()[0]);
let crtc = d.get_crtc(crtc_handle)?;
let plane = Self::pick_primary_plane(&d, &res, crtc.handle())?;
let con_props = d
.get_properties(connector.handle())?
.as_hashmap(d.as_ref())?;
let crtc_props = d.get_properties(crtc.handle())?.as_hashmap(d.as_ref())?;
let plane_props = d.get_properties(plane)?.as_hashmap(d.as_ref())?;
let props = AtomicProps {
conn_crtc_id: con_props["CRTC_ID"].handle(),
crtc_mode_id: crtc_props["MODE_ID"].handle(),
crtc_active: crtc_props["ACTIVE"].handle(),
plane_fb_id: plane_props["FB_ID"].handle(),
plane_crtc_id: plane_props["CRTC_ID"].handle(),
plane_src_x: plane_props["SRC_X"].handle(),
plane_src_y: plane_props["SRC_Y"].handle(),
plane_src_w: plane_props["SRC_W"].handle(),
plane_src_h: plane_props["SRC_H"].handle(),
plane_crtc_x: plane_props["CRTC_X"].handle(),
plane_crtc_y: plane_props["CRTC_Y"].handle(),
plane_crtc_w: plane_props["CRTC_W"].handle(),
plane_crtc_h: plane_props["CRTC_H"].handle(),
};
let (tx, rx) = mpsc::sync_channel::<control::Event>(3);
let rd_jh = d.clone();
let jh = thread::spawn(move || -> Result<()> {
println!("Event thread started.");
loop {
let events = rd_jh
.as_ref()
.receive_events()
.context("receiving DRM events")?;
for ev in events {
tx.send(ev)?;
}
}
});
Ok(Self {
d,
fb: None,
connector,
mode,
crtc,
plane,
props,
srx: rx,
event_handle: jh,
})
}
fn awake(&self) -> Result<()> {
self.atomic_commit(
AtomicCommitFlags::ALLOW_MODESET
| AtomicCommitFlags::PAGE_FLIP_EVENT
| AtomicCommitFlags::NONBLOCK,
)
}
fn commit(&self) -> Result<()> {
self.atomic_commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK)
}
fn set_state(&mut self, state: Rc<Framebuffer<DrmCard>>) -> Result<()> {
self.fb = Some(state);
Ok(())
}
fn sync(&self) -> Result<()> {
while let Ok(ev) = self.srx.recv() {
match ev {
control::Event::PageFlip(e) if e.crtc == self.crtc.handle() => {
return Ok(());
}
control::Event::Vblank(e) if e.crtc == self.crtc.handle() => {
return Ok(());
}
_ => continue,
}
}
bail!("Event channel disconnected")
}
}