use std::os::fd::OwnedFd;
use drm::control::Device as ControlDevice;
use drmkit_core::{
AtomicCommitFlags, AtomicRequest, Device, Mode, ObjectType, PropertyBlob, PropertyStore,
commit_with_out_fence,
};
use drmkit_display::{DriverProfile, ScanoutTarget};
use drmkit_modeset::ModeInfo as _;
use drmkit_planes::PlaneRegistry;
use drmkit_scene::{
CommitKind, CommitReport, DeviceCommitter, DisplayParams, KernelResult, LayerHandle,
LayerScene, Modeset, PlanePropertyMap, arm_acquire_fences, emit_frame_damaged,
};
use drmkit_sync::SyncFence;
use crate::{FrameAction, FrameEconomy, PresentError, ScanoutProducer, negotiate};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum VrrPolicy {
#[default]
Off,
Auto,
On,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum RestorePolicy {
#[default]
None,
SavedCrtc,
}
#[derive(Debug, Clone, Copy)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct Config {
pub fourcc: u32,
pub rotation: drmkit_fmt::Rotation,
pub vrr: VrrPolicy,
pub restore: RestorePolicy,
pub connector_id: u32,
}
impl Default for Config {
fn default() -> Self {
Self {
fourcc: drmkit_fmt::fourcc::XRGB8888,
rotation: drmkit_fmt::Rotation::Rotate0,
vrr: VrrPolicy::Off,
restore: RestorePolicy::None,
connector_id: 0,
}
}
}
#[derive(Debug, Clone, Copy)]
struct SavedCrtc {
handle: drm::control::crtc::Handle,
framebuffer: Option<drm::control::framebuffer::Handle>,
position: (u32, u32),
mode: Option<Mode>,
}
pub struct ScanoutBackend {
target: ScanoutTarget,
profile: DriverProfile,
modifiers: Vec<u64>,
scene: LayerScene,
layer: LayerHandle,
registry: PlaneRegistry,
map: PlanePropertyMap,
economy: FrameEconomy,
crtc_index: u32,
vrr_property: Option<u32>,
vrr_wanted: bool,
vrr_armed: bool,
out_fence_property: Option<u32>,
needs_modeset: bool,
restore: RestorePolicy,
saved: Option<SavedCrtc>,
device_fd: std::os::fd::RawFd,
armed_flip_seq: Option<crate::armed_flip::ArmedFlip>,
}
impl std::fmt::Debug for ScanoutBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ScanoutBackend")
.field("driver", &self.profile.name)
.field("crtc_id", &self.target.crtc.id)
.field("modifiers", &self.modifiers.len())
.field("vrr", &self.vrr_armed)
.finish_non_exhaustive()
}
}
impl ScanoutBackend {
pub fn create(
device: &Device,
producer: &mut dyn ScanoutProducer,
config: &Config,
) -> Result<Self, PresentError> {
let target = ScanoutTarget::discover_connector(device, config.connector_id)
.map_err(|error| PresentError::Bind(format!("discovering an output: {error}")))?
.map_err(|error| PresentError::Bind(format!("discovering an output: {error}")))?;
let profile = DriverProfile::probe(device)
.map_err(|error| PresentError::Bind(format!("probing the driver: {error}")))?;
let crtc_id = target.crtc.id;
let crtc_index = target.crtc.index;
let (width, height) = (target.mode.width(), target.mode.height());
let registry = PlaneRegistry::probe(device)
.map_err(|error| PresentError::Bind(format!("probing planes: {error}")))?;
let mut map = PlanePropertyMap::new();
for plane in registry.for_crtc(crtc_index) {
map.learn_plane(device, plane.id)
.map_err(|error| PresentError::Bind(format!("plane {}: {error}", plane.id)))?;
}
let (modifiers, allowed) =
Self::negotiate_modifiers(®istry, crtc_index, producer, config);
let source = producer
.create_buffer(width, height, config.fourcc, &allowed)
.map_err(|error| PresentError::Bind(format!("allocating: {error}")))?;
let mut scene = LayerScene::new(crtc_id);
let layer = scene.add_layer(source);
scene
.layer_mut(layer)
.ok_or_else(|| PresentError::Bind("the layer vanished".to_owned()))?
.set_display(DisplayParams {
src_rect: drmkit_planes::Rect {
x: 0,
y: 0,
w: width,
h: height,
},
dst_rect: drmkit_planes::Rect {
x: 0,
y: 0,
w: width,
h: height,
},
..DisplayParams::default()
});
let mut store = PropertyStore::new();
let (vrr_property, out_fence_property) = if store
.cache_properties(device, crtc_id, ObjectType::Crtc)
.is_ok()
{
(
store.property_id(crtc_id, "VRR_ENABLED").ok(),
store.property_id(crtc_id, "OUT_FENCE_PTR").ok(),
)
} else {
(None, None)
};
let vrr_wanted = match config.vrr {
VrrPolicy::Off => false,
VrrPolicy::On => true,
VrrPolicy::Auto => profile.vrr_capable,
};
let saved = (config.restore == RestorePolicy::SavedCrtc)
.then(|| Self::snapshot_crtc(device, crtc_id))
.flatten();
Ok(Self {
target,
profile,
modifiers,
scene,
layer,
registry,
map,
economy: FrameEconomy::new(),
crtc_index,
vrr_property,
vrr_wanted,
vrr_armed: false,
out_fence_property,
needs_modeset: true,
restore: config.restore,
saved,
device_fd: device.raw_fd(),
armed_flip_seq: None,
})
}
fn negotiate_modifiers(
registry: &PlaneRegistry,
crtc_index: u32,
producer: &mut dyn ScanoutProducer,
config: &Config,
) -> (Vec<u64>, Vec<u64>) {
let plane_modifiers = registry.candidate_modifiers(crtc_index, config.fourcc);
let producer_modifiers = producer.exportable_modifiers(config.fourcc);
let modifiers: Vec<u64> = if plane_modifiers.is_empty() {
Vec::new()
} else {
let producer: Vec<drmkit_fmt::Modifier> = producer_modifiers
.iter()
.copied()
.map(drmkit_fmt::Modifier)
.collect();
let planes: Vec<drmkit_fmt::Modifier> = plane_modifiers
.iter()
.copied()
.map(drmkit_fmt::Modifier)
.collect();
negotiate(&producer, &planes, config.rotation)
.into_iter()
.map(|modifier| modifier.0)
.collect()
};
let allowed = if modifiers.is_empty() {
vec![drmkit_fmt::Modifier::LINEAR.0]
} else {
modifiers.clone()
};
(modifiers, allowed)
}
fn snapshot_crtc(device: &Device, crtc_id: u32) -> Option<SavedCrtc> {
let handle = drm::control::crtc::Handle::from(std::num::NonZeroU32::new(crtc_id)?);
let info = device.get_crtc(handle).ok()?;
Some(SavedCrtc {
handle,
framebuffer: info.framebuffer(),
position: info.position(),
mode: info.mode(),
})
}
pub fn present(
&mut self,
device: &Device,
flags: AtomicCommitFlags,
out_fence: Option<&mut Option<OwnedFd>>,
) -> Result<CommitReport, PresentError> {
self.economy.force_full();
let _ = self.economy.decide(true, false);
self.commit(device, flags, out_fence)
}
pub fn present_if_changed(
&mut self,
device: &Device,
content_changed: bool,
flags: AtomicCommitFlags,
out_fence: Option<&mut Option<OwnedFd>>,
) -> Result<CommitReport, PresentError> {
match self.economy.decide(content_changed, false) {
FrameAction::Skip => Ok(CommitReport::suppressed()),
FrameAction::CommitFull | FrameAction::CommitDamaged => {
self.commit(device, flags, out_fence)
}
}
}
fn commit(
&mut self,
device: &Device,
flags: AtomicCommitFlags,
out_fence: Option<&mut Option<OwnedFd>>,
) -> Result<CommitReport, PresentError> {
let modeset = if self.needs_modeset {
Some(
Modeset::learn(
device,
self.target.crtc.id,
self.target.connector_id,
&self.target.mode,
)
.map_err(|error| PresentError::Bind(format!("learning the mode: {error}")))?,
)
} else {
None
};
let vrr_changing = self.vrr_property.is_some() && self.vrr_wanted != self.vrr_armed;
let mut flags = flags;
if modeset.is_some() || vrr_changing {
flags |= AtomicCommitFlags::ALLOW_MODESET;
}
let mut committer = DeviceCommitter::new(
device,
&self.map,
&self.registry,
self.crtc_index,
AtomicCommitFlags::empty(),
modeset.as_ref(),
);
let arms_flip = flags.contains(AtomicCommitFlags::PAGE_FLIP_EVENT);
let mut build = self.scene.build_frame(
&self.registry,
self.crtc_index,
CommitKind::Real { arms_flip },
&mut committer,
)?;
let crtc_id = self.target.crtc.id;
let vrr = (self.vrr_property, self.vrr_wanted);
let wanted_internally = self.scene.wants_release_fence();
let out_fence_property = (out_fence.is_some() || wanted_internally)
.then_some(self.out_fence_property)
.flatten();
let programmed: Vec<_> = build
.plan()
.iter()
.map(|entry| (entry.plane_id, entry.color))
.collect();
let emit = |request: &mut AtomicRequest,
slot: u64|
-> Result<Vec<PropertyBlob<'_>>, drmkit_core::CoreError> {
let (_, damage_blobs) = emit_frame_damaged(
request,
&self.map,
&mut build,
modeset.as_ref(),
Some(device),
)?;
arm_acquire_fences(&mut build, request, &self.map, true)?;
if let (Some(property), wanted) = vrr {
request.add_property(crtc_id, property, u64::from(wanted))?;
}
if let Some(property) = out_fence_property {
request.add_property(crtc_id, property, slot)?;
}
Ok(damage_blobs)
};
let outcome = commit_with_out_fence(device, flags, emit);
let fence = match outcome {
Ok(fence) => fence,
Err(error) => {
self.scene.finalize_frame(build, KernelResult::Rejected);
return Err(PresentError::Commit(error));
}
};
let release_fence = fence
.as_ref()
.and_then(|fd| SyncFence::import(std::os::fd::AsFd::as_fd(fd)).ok());
if let Some(slot) = out_fence {
*slot = fence;
}
for (plane_id, color) in programmed {
self.map.note_color_committed(plane_id, color);
}
self.needs_modeset = false;
self.vrr_armed = self.vrr_wanted;
self.armed_flip_seq = crate::armed_flip::armed_sequence(device, crtc_id, arms_flip);
Ok(self
.scene
.finalize_frame_with_fence(build, KernelResult::Ok, release_fence.as_ref()))
}
pub const fn set_vrr(&mut self, enable: bool) {
self.vrr_wanted = enable;
}
#[must_use]
pub const fn vrr_capable(&self) -> bool {
self.profile.vrr_capable
}
pub const fn force_full_present(&mut self) {
self.economy.force_full();
}
#[must_use]
pub const fn frames_committed(&self) -> u64 {
self.economy.committed()
}
#[must_use]
pub const fn frames_skipped(&self) -> u64 {
self.economy.skipped()
}
#[must_use]
pub const fn target(&self) -> &ScanoutTarget {
&self.target
}
#[must_use]
pub const fn profile(&self) -> &DriverProfile {
&self.profile
}
#[must_use]
pub fn modifiers(&self) -> &[u64] {
&self.modifiers
}
#[must_use]
pub const fn scene(&self) -> &LayerScene {
&self.scene
}
pub const fn scene_mut(&mut self) -> &mut LayerScene {
&mut self.scene
}
#[must_use]
pub const fn layer(&self) -> LayerHandle {
self.layer
}
fn restore_connector(&self) -> Vec<drm::control::connector::Handle> {
std::num::NonZeroU32::new(self.target.connector_id)
.map(drm::control::connector::Handle::from)
.into_iter()
.collect()
}
pub fn flip_landed(&mut self) {
self.scene.flip_landed();
}
}
impl Drop for ScanoutBackend {
fn drop(&mut self) {
let borrowed = unsafe { std::os::fd::BorrowedFd::borrow_raw(self.device_fd) };
crate::armed_flip::settle(
&mut self.scene,
&borrowed,
self.target.crtc.id,
self.armed_flip_seq,
);
if self.restore != RestorePolicy::SavedCrtc {
return;
}
let Some(saved) = self.saved else { return };
drop(std::mem::replace(
&mut self.scene,
LayerScene::new(self.target.crtc.id),
));
let device = RawDevice { fd: borrowed };
let (connectors, mode) = if saved.framebuffer.is_some() {
(self.restore_connector(), saved.mode)
} else {
(Vec::new(), None)
};
let _ = device.set_crtc(
saved.handle,
saved.framebuffer,
saved.position,
&connectors,
mode,
);
}
}
struct RawDevice<'a> {
fd: std::os::fd::BorrowedFd<'a>,
}
impl std::os::fd::AsFd for RawDevice<'_> {
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
self.fd
}
}
impl drm::Device for RawDevice<'_> {}
impl ControlDevice for RawDevice<'_> {}