use std::cell::RefCell;
use std::rc::Rc;
use drmkit_core::{AtomicCommitFlags, AtomicRequest, Device, Mode};
use drmkit_modeset::ModeInfo as _;
use drmkit_planes::PlaneRegistry;
use drmkit_scene::{
AcquiredBuffer, BindingModel, CommitKind, CommitReport, DamageRect, DeviceCommitter,
DisplayParams, KernelResult, LayerBufferSource, LayerHandle, LayerScene, Modeset,
PlanePropertyMap, SourceError, SourceFormat, arm_acquire_fences, emit_frame_damaged,
};
use crate::{DumbRingSource, PaintError, Rect};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum PresentError {
#[error("a {height}-row frame at stride {stride} needs {needed} bytes, got {got}")]
ShortFrame {
height: u32,
stride: u32,
needed: usize,
got: usize,
},
#[error("every ring slot is busy")]
WouldBlock,
#[error("painting: {0}")]
Paint(PaintError),
#[error("building the frame: {0}")]
Build(#[from] drmkit_scene::SceneError),
#[error("committing: {0}")]
Commit(#[from] drmkit_core::CoreError),
#[error("binding the output: {0}")]
Bind(String),
}
impl From<PaintError> for PresentError {
fn from(error: PaintError) -> Self {
match error {
PaintError::WouldBlock => Self::WouldBlock,
other => Self::Paint(other),
}
}
}
#[derive(Clone)]
struct SharedRing(Rc<RefCell<DumbRingSource>>);
impl LayerBufferSource for SharedRing {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
self.0.borrow_mut().acquire()
}
fn release(&mut self, acquired: AcquiredBuffer) {
self.0.borrow_mut().release(acquired);
}
fn binding_model(&self) -> BindingModel {
self.0.borrow().binding_model()
}
fn format(&self) -> SourceFormat {
self.0.borrow().format()
}
fn on_session_paused(&mut self) {
self.0.borrow_mut().on_session_paused();
}
fn on_session_resumed(&mut self, device: &Device) -> Result<(), SourceError> {
self.0.borrow_mut().on_session_resumed(device)
}
}
#[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 format: Option<u32>,
pub buffers: usize,
}
impl Default for Config {
fn default() -> Self {
Self {
format: None,
buffers: 3,
}
}
}
pub struct DumbScanoutSink {
scene: LayerScene,
ring: Rc<RefCell<DumbRingSource>>,
registry: PlaneRegistry,
map: PlanePropertyMap,
crtc_id: u32,
crtc_index: u32,
connector_id: u32,
mode: Mode,
width: u32,
height: u32,
format: u32,
needs_modeset: bool,
layer: LayerHandle,
device_fd: std::os::fd::RawFd,
armed_flip_seq: Option<crate::armed_flip::ArmedFlip>,
}
pub(crate) fn frame_span(height: u32, stride: u32) -> Option<usize> {
usize::try_from(height)
.ok()?
.checked_mul(usize::try_from(stride).ok()?)
}
impl DumbScanoutSink {
pub fn create(
device: &Device,
crtc_id: u32,
crtc_index: u32,
connector_id: u32,
mode: &Mode,
config: &Config,
) -> Result<Self, PresentError> {
let format = config.format.unwrap_or_else(|| {
crate::negotiate_scanout_format(device, crtc_id, &[])
.unwrap_or(drmkit_fmt::fourcc::XRGB8888)
});
let (width, height) = (mode.width(), mode.height());
let buffers = if config.buffers == 0 {
3
} else {
config.buffers
};
let ring = Rc::new(RefCell::new(DumbRingSource::new(
width, height, format, buffers,
)?));
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 mut scene = LayerScene::new(crtc_id);
let layer = scene.add_layer(Box::new(SharedRing(Rc::clone(&ring))));
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()
});
Ok(Self {
scene,
ring,
registry,
map,
crtc_id,
crtc_index,
connector_id,
mode: *mode,
width,
height,
format,
needs_modeset: true,
layer,
device_fd: device.raw_fd(),
armed_flip_seq: None,
})
}
pub fn present(
&mut self,
device: &Device,
src: &[u8],
src_stride: u32,
damage: &[DamageRect],
flags: AtomicCommitFlags,
) -> Result<CommitReport, PresentError> {
let needed = frame_span(self.height, src_stride);
if needed.is_none_or(|needed| src.len() < needed) {
return Err(PresentError::ShortFrame {
height: self.height,
stride: src_stride,
needed: needed.unwrap_or(usize::MAX),
got: src.len(),
});
}
let (width, height) = (self.width, self.height);
self.ring.borrow_mut().paint(device, |mapping, _repaint| {
for y in 0..height {
let Some(row) = mapping.row_mut(y) else {
break;
};
let from = y as usize * src_stride as usize;
let bytes = row.len().min(src_stride as usize);
row[..bytes].copy_from_slice(&src[from..from + bytes]);
}
if damage.is_empty() {
vec![Rect {
x: 0,
y: 0,
width,
height,
}]
} else {
damage
.iter()
.map(|d| Rect {
x: d.x,
y: d.y,
width: d.w,
height: d.h,
})
.collect()
}
})?;
self.commit(device, flags)
}
fn commit(
&mut self,
device: &Device,
flags: AtomicCommitFlags,
) -> Result<CommitReport, PresentError> {
let modeset = if self.needs_modeset {
Some(
Modeset::learn(device, self.crtc_id, self.connector_id, &self.mode)
.map_err(|error| PresentError::Bind(format!("learning the mode: {error}")))?,
)
} else {
None
};
let mut flags = flags;
if modeset.is_some() {
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 mut request = AtomicRequest::with_capacity(64);
let (_, _damage_blobs) = emit_frame_damaged(
&mut request,
&self.map,
&mut build,
modeset.as_ref(),
Some(device),
)?;
arm_acquire_fences(&mut build, &mut request, &self.map, true)?;
let programmed: Vec<_> = build
.plan()
.iter()
.map(|entry| (entry.plane_id, entry.color))
.collect();
let result = match request.commit(device, flags) {
Ok(()) => KernelResult::Ok,
Err(error) => {
self.scene.finalize_frame(build, KernelResult::Rejected);
return Err(PresentError::Commit(error));
}
};
for (plane_id, color) in programmed {
self.map.note_color_committed(plane_id, color);
}
self.needs_modeset = false;
self.armed_flip_seq = crate::armed_flip::armed_sequence(device, self.crtc_id, arms_flip);
Ok(self.scene.finalize_frame(build, result))
}
pub fn flip_landed(&mut self) {
self.scene.flip_landed();
}
#[must_use]
pub const fn size(&self) -> (u32, u32) {
(self.width, self.height)
}
#[must_use]
pub const fn format(&self) -> u32 {
self.format
}
#[must_use]
pub const fn layer(&self) -> LayerHandle {
self.layer
}
#[must_use]
pub const fn scene(&self) -> &LayerScene {
&self.scene
}
pub const fn scene_mut(&mut self) -> &mut LayerScene {
&mut self.scene
}
}
impl Drop for DumbScanoutSink {
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.crtc_id,
self.armed_flip_seq,
);
}
}