use std::collections::HashMap;
use drmkit_core::{
AtomicCommitFlags, CoreError, Device, Mode, ObjectType, PropertyBlob, PropertyStore,
};
use drmkit_planes::{
ColorEncoding, ColorEncodingValues, ColorRange, ColorRangeValues, LayerRef, PropTag,
TestCommitter, TestFailure,
};
use crate::frame::KernelResult;
#[derive(Debug, Clone, Copy, Default)]
struct ColorProps {
encoding: Option<(u32, ColorEncodingValues)>,
range: Option<(u32, ColorRangeValues)>,
}
impl ColorProps {
fn resolve(&self, want: crate::Ycbcr) -> ResolvedColor {
ResolvedColor {
encoding: self
.encoding
.and_then(|(id, values)| Some((id, encoding_value(&values, want.encoding)?))),
range: self
.range
.and_then(|(id, values)| Some((id, range_value(&values, want.range)?))),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct ResolvedColor {
encoding: Option<(u32, u64)>,
range: Option<(u32, u64)>,
}
pub(crate) fn encoding_value(values: &ColorEncodingValues, want: ColorEncoding) -> Option<u64> {
let exact = match want {
ColorEncoding::Bt601 => values.bt601,
ColorEncoding::Bt709 => values.bt709,
ColorEncoding::Bt2020 => values.bt2020,
};
exact.or(values.bt709).or(values.bt601)
}
pub(crate) fn range_value(values: &ColorRangeValues, want: ColorRange) -> Option<u64> {
let exact = match want {
ColorRange::Limited => values.limited,
ColorRange::Full => values.full,
};
exact.or(values.limited).or(values.full)
}
#[derive(Debug, Default)]
pub struct PlanePropertyMap {
pub(crate) planes: HashMap<u32, HashMap<PropTag, u32>>,
immutable: HashMap<u32, Vec<PropTag>>,
color: HashMap<u32, ColorProps>,
damage: HashMap<u32, u32>,
pub(crate) zpos_range: HashMap<u32, (u64, u64)>,
pub(crate) alpha_max: HashMap<u32, u64>,
color_committed: HashMap<u32, ResolvedColor>,
}
impl PlanePropertyMap {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn learn_plane(&mut self, device: &Device, plane_id: u32) -> Result<(), CoreError> {
let mut store = PropertyStore::new();
store.cache_properties(device, plane_id, ObjectType::Plane)?;
let writable = |name: &str| {
store
.property_id(plane_id, name)
.ok()
.filter(|_| !store.is_immutable(plane_id, name).unwrap_or(false))
};
let entry =
|name: &str, variant: &str| store.enum_value(device, plane_id, name, variant).ok();
let color = ColorProps {
encoding: writable("COLOR_ENCODING").map(|id| {
(
id,
ColorEncodingValues {
bt601: entry("COLOR_ENCODING", "ITU-R BT.601 YCbCr"),
bt709: entry("COLOR_ENCODING", "ITU-R BT.709 YCbCr"),
bt2020: entry("COLOR_ENCODING", "ITU-R BT.2020 YCbCr"),
},
)
}),
range: writable("COLOR_RANGE").map(|id| {
(
id,
ColorRangeValues {
limited: entry("COLOR_RANGE", "YCbCr limited range"),
full: entry("COLOR_RANGE", "YCbCr full range"),
},
)
}),
};
if color.encoding.is_some() || color.range.is_some() {
self.color.insert(plane_id, color);
}
if let Ok(Some(range)) = store.range(plane_id, PropTag::Zpos.name()) {
self.zpos_range.insert(plane_id, range);
}
if let Ok(Some((_, max))) = store.range(plane_id, PropTag::Alpha.name()) {
self.alpha_max.insert(plane_id, max);
}
if let Ok(id) = store.property_id(plane_id, "FB_DAMAGE_CLIPS") {
self.damage.insert(plane_id, id);
}
let mut ids = HashMap::new();
let mut immutable = Vec::new();
for tag in PropTag::ALL {
if matches!(tag, PropTag::PixelFormat | PropTag::FbModifier) {
continue;
}
if let Ok(id) = store.property_id(plane_id, tag.name()) {
if store.is_immutable(plane_id, tag.name()).unwrap_or(false) {
immutable.push(tag);
continue;
}
ids.insert(tag, id);
}
}
self.planes.insert(plane_id, ids);
self.immutable.insert(plane_id, immutable);
Ok(())
}
pub fn learn_all(
&mut self,
device: &Device,
registry: &drmkit_planes::PlaneRegistry,
) -> Result<(), CoreError> {
for plane in registry.all() {
self.learn_plane(device, plane.id)?;
}
Ok(())
}
pub fn note_color_committed(&mut self, plane_id: u32, color: Option<crate::Ycbcr>) {
if let (Some(want), Some(props)) = (color, self.color.get(&plane_id)) {
self.color_committed.insert(plane_id, props.resolve(want));
}
}
#[must_use]
pub fn property_id(&self, plane_id: u32, tag: PropTag) -> Option<u32> {
self.planes.get(&plane_id)?.get(&tag).copied()
}
#[must_use]
pub fn is_immutable(&self, plane_id: u32, tag: PropTag) -> bool {
self.immutable
.get(&plane_id)
.is_some_and(|tags| tags.contains(&tag))
}
#[must_use]
pub fn len(&self) -> usize {
self.planes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.planes.is_empty()
}
}
pub fn emit_layer(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
plane_id: u32,
layer: &drmkit_planes::Layer,
baseline: Option<&drmkit_planes::PropertySnapshot>,
) -> Result<LayerWrites, CoreError> {
let mut written = LayerWrites::default();
for (tag, value) in layer.properties() {
let Some(property_id) = map.property_id(plane_id, tag) else {
continue;
};
if layer.is_externally_bound() && tag == PropTag::FbId {
continue;
}
if !needs_write(tag, value, baseline) {
continue;
}
let value = match tag {
PropTag::Zpos => map.clamp_zpos(plane_id, value),
PropTag::Alpha => map.rescale_alpha(plane_id, value),
_ => value,
};
request.add_property(plane_id, property_id, value)?;
written.properties += 1;
if tag == PropTag::FbId {
written.framebuffers += 1;
}
}
Ok(written)
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct LayerWrites {
pub properties: usize,
pub framebuffers: usize,
}
impl PlanePropertyMap {
#[must_use]
pub fn rescale_alpha(&self, plane_id: u32, value: u64) -> u64 {
self.alpha_max
.get(&plane_id)
.map_or(value, |&max| drmkit_planes::rescale_alpha(value, max))
}
#[must_use]
pub fn clamp_zpos(&self, plane_id: u32, value: u64) -> u64 {
match self.zpos_range.get(&plane_id) {
Some((lo, hi)) => value.clamp(*lo, *hi),
None => value,
}
}
}
fn needs_write(
tag: PropTag,
value: u64,
baseline: Option<&drmkit_planes::PropertySnapshot>,
) -> bool {
if matches!(tag, PropTag::FbId | PropTag::InFenceFd) {
return true;
}
baseline.is_none_or(|snapshot| snapshot.get(tag) != Some(value))
}
pub fn emit_disable(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
plane_id: u32,
) -> Result<usize, CoreError> {
let mut written = 0;
for tag in [PropTag::FbId, PropTag::CrtcId] {
if let Some(property_id) = map.property_id(plane_id, tag) {
request.add_property(plane_id, property_id, 0)?;
written += 1;
}
}
Ok(written)
}
pub fn commit_detach(device: &Device, scene: &mut crate::LayerScene) -> Result<bool, CoreError> {
let Some(detach) = scene.pending_detach() else {
return Ok(false);
};
let mut store = PropertyStore::new();
let mut request = drmkit_core::AtomicRequest::with_capacity(2 * detach.planes.len() + 1);
for &plane_id in &detach.planes {
store.cache_properties(device, plane_id, ObjectType::Plane)?;
request.add_property(plane_id, store.property_id(plane_id, "FB_ID")?, 0)?;
request.add_property(plane_id, store.property_id(plane_id, "CRTC_ID")?, 0)?;
}
store.cache_properties(device, detach.crtc_id, ObjectType::Crtc)?;
request.add_property(
detach.crtc_id,
store.property_id(detach.crtc_id, "ACTIVE")?,
0,
)?;
request.commit(device, AtomicCommitFlags::ALLOW_MODESET)?;
scene.detach_committed();
Ok(true)
}
fn with_stacked_zpos(
layer: &drmkit_planes::Layer,
plane_id: u32,
stacked: &[(u32, u64)],
) -> drmkit_planes::Layer {
let mut layer = layer.clone();
if let Some((_, zpos)) = stacked.iter().find(|(id, _)| *id == plane_id) {
layer.set_property(PropTag::Zpos, *zpos);
}
layer
}
pub struct DeviceCommitter<'a> {
device: &'a Device,
map: &'a PlanePropertyMap,
candidates: Vec<u32>,
modeset: Option<&'a Modeset<'a>>,
flags: AtomicCommitFlags,
pub commits: usize,
extra: Option<(u32, drmkit_planes::Layer)>,
registry: &'a drmkit_planes::PlaneRegistry,
}
impl<'a> DeviceCommitter<'a> {
#[must_use]
pub fn new(
device: &'a Device,
map: &'a PlanePropertyMap,
registry: &'a drmkit_planes::PlaneRegistry,
crtc_index: u32,
flags: AtomicCommitFlags,
modeset: Option<&'a Modeset<'a>>,
) -> Self {
Self {
device,
map,
candidates: registry
.force_disable_candidates(crtc_index)
.map(|plane| plane.id)
.collect(),
flags,
modeset,
commits: 0,
extra: None,
registry,
}
}
}
impl TestCommitter for DeviceCommitter<'_> {
fn lit_planes(&mut self, crtc_id: u32) -> Option<Vec<u32>> {
let mut store = drmkit_core::PropertyStore::new();
let mut lit = Vec::new();
for &plane_id in &self.candidates {
store
.cache_properties(self.device, plane_id, drmkit_core::ObjectType::Plane)
.ok()?;
let value = |name| store.property_value(plane_id, name).ok();
if value("FB_ID").unwrap_or(0) != 0 && value("CRTC_ID") == Some(u64::from(crtc_id)) {
lit.push(plane_id);
}
}
Some(lit)
}
fn test_assignment(&mut self, assignment: &[(u32, LayerRef<'_>)]) -> Result<(), TestFailure> {
self.commits += 1;
let mut request = drmkit_core::AtomicRequest::new();
if let Some(modeset) = self.modeset
&& modeset.emit(&mut request).is_err()
{
return Err(TestFailure::Rejected);
}
let extra_plane = self.extra.as_ref().map(|(plane_id, _)| *plane_id);
for plane_id in &self.candidates {
if assignment.iter().all(|(assigned, _)| assigned != plane_id)
&& extra_plane != Some(*plane_id)
&& emit_disable(&mut request, self.map, *plane_id).is_err()
{
return Err(TestFailure::Rejected);
}
}
let mut armed: Vec<(u32, Option<u64>)> = assignment
.iter()
.map(|(plane_id, layer)| (*plane_id, layer.layer.property(PropTag::Zpos)))
.collect();
if let Some((plane_id, layer)) = &self.extra {
armed.push((*plane_id, layer.property(PropTag::Zpos)));
}
let stacked = drmkit_planes::stacked_zpos(self.registry, &armed);
for (plane_id, layer) in assignment {
let layer = with_stacked_zpos(layer.layer, *plane_id, &stacked);
if emit_layer(&mut request, self.map, *plane_id, &layer, None).is_err() {
return Err(TestFailure::Rejected);
}
}
if let Some((plane_id, layer)) = &self.extra {
let layer = with_stacked_zpos(layer, *plane_id, &stacked);
if emit_layer(&mut request, self.map, *plane_id, &layer, None).is_err() {
return Err(TestFailure::Rejected);
}
}
let flags = if self.modeset.is_some() {
self.flags | AtomicCommitFlags::ALLOW_MODESET
} else {
self.flags
};
match request.test(self.device, flags) {
Ok(()) => Ok(()),
Err(CoreError::NotMaster) => Err(TestFailure::NotMaster),
Err(_) => Err(TestFailure::Rejected),
}
}
fn set_extra_plane(&mut self, plane_id: u32, layer: drmkit_planes::Layer) {
self.extra = Some((plane_id, layer));
}
fn clear_extra_plane(&mut self) {
self.extra = None;
}
}
#[must_use]
pub fn classify(result: &Result<(), CoreError>) -> KernelResult {
match result {
Ok(()) => KernelResult::Ok,
Err(CoreError::NotMaster) => KernelResult::NotMaster,
Err(_) => KernelResult::Rejected,
}
}
pub struct Modeset<'a> {
crtc_id: u32,
connector_id: u32,
crtc_active: u32,
crtc_mode_id: u32,
connector_crtc_id: u32,
mode_blob: PropertyBlob<'a>,
}
impl<'a> Modeset<'a> {
pub fn learn(
device: &'a Device,
crtc_id: u32,
connector_id: u32,
mode: &Mode,
) -> Result<Self, CoreError> {
let mut store = PropertyStore::new();
store.cache_properties(device, crtc_id, ObjectType::Crtc)?;
store.cache_properties(device, connector_id, ObjectType::Connector)?;
let crtc_active = store.property_id(crtc_id, "ACTIVE")?;
let crtc_mode_id = store.property_id(crtc_id, "MODE_ID")?;
let connector_crtc_id = store.property_id(connector_id, "CRTC_ID")?;
let mode_blob = device.create_property_blob(mode)?;
Ok(Self {
crtc_id,
connector_id,
crtc_active,
crtc_mode_id,
connector_crtc_id,
mode_blob,
})
}
pub fn emit(&self, request: &mut drmkit_core::AtomicRequest) -> Result<usize, CoreError> {
request.add_property(self.crtc_id, self.crtc_mode_id, self.mode_blob.id())?;
request.add_property(self.crtc_id, self.crtc_active, 1)?;
request.add_property(
self.connector_id,
self.connector_crtc_id,
u64::from(self.crtc_id),
)?;
Ok(3)
}
#[must_use]
pub const fn mode_blob(&self) -> u64 {
self.mode_blob.id()
}
}
pub fn emit_frame(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
build: &mut crate::FrameBuild,
modeset: Option<&Modeset<'_>>,
) -> Result<usize, CoreError> {
emit_frame_damaged(request, map, build, modeset, None).map(|(written, _)| written)
}
pub fn emit_frame_damaged<'a>(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
build: &mut crate::FrameBuild,
modeset: Option<&Modeset<'_>>,
device: Option<&'a Device>,
) -> Result<(usize, Vec<drmkit_core::PropertyBlob<'a>>), CoreError> {
let mut written = 0;
let mut fbs = 0;
let mut damaged = 0;
let mut blobs = Vec::new();
if let Some(modeset) = modeset {
written += modeset.emit(request)?;
}
for plane_id in build.disables() {
written += emit_disable(request, map, *plane_id)?;
}
for entry in build.plan() {
let bag = match entry.zpos {
Some(zpos) if entry.layer.property(PropTag::Zpos) != Some(zpos) => {
let mut bag = entry.layer.clone();
bag.set_property(PropTag::Zpos, zpos);
std::borrow::Cow::Owned(bag)
}
_ => std::borrow::Cow::Borrowed(&entry.layer),
};
let layer = emit_layer(request, map, entry.plane_id, &bag, entry.baseline.as_ref())?;
written += layer.properties;
fbs += layer.framebuffers;
written += emit_color_props(
request,
map,
entry.plane_id,
entry.color,
build.restates_sticky(),
)?;
if let Some(device) = device
&& let Some(blob) = emit_damage(request, map, device, entry.plane_id, &entry.damage)?
{
blobs.push(blob);
written += 1;
damaged += 1;
}
}
build.record_emission(written, fbs, damaged);
Ok((written, blobs))
}
pub(crate) const MAX_DAMAGE_RECTS: usize = 16;
pub(crate) fn clip_damage(
damage: &[crate::DamageRect],
width: u32,
height: u32,
) -> Vec<crate::DamageRect> {
if damage.len() > MAX_DAMAGE_RECTS {
return Vec::new();
}
damage
.iter()
.filter_map(|rect| {
let x1 = i64::from(rect.x).max(0);
let y1 = i64::from(rect.y).max(0);
let x2 = (i64::from(rect.x) + i64::from(rect.w)).min(i64::from(width));
let y2 = (i64::from(rect.y) + i64::from(rect.h)).min(i64::from(height));
if x2 <= x1 || y2 <= y1 {
return None;
}
Some(crate::DamageRect {
x: i32::try_from(x1).ok()?,
y: i32::try_from(y1).ok()?,
w: u32::try_from(x2 - x1).ok()?,
h: u32::try_from(y2 - y1).ok()?,
})
})
.collect()
}
pub(crate) fn damage_rects(damage: &[crate::DamageRect]) -> Vec<[i32; 4]> {
damage
.iter()
.map(|rect| {
[
rect.x,
rect.y,
rect.x.saturating_add(rect.w.cast_signed()),
rect.y.saturating_add(rect.h.cast_signed()),
]
})
.collect()
}
fn emit_damage<'a>(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
device: &'a Device,
plane_id: u32,
damage: &[crate::DamageRect],
) -> Result<Option<drmkit_core::PropertyBlob<'a>>, CoreError> {
let Some(property_id) = map.damage.get(&plane_id).copied() else {
return Ok(None);
};
if damage.is_empty() {
return Ok(None);
}
let rects = damage_rects(damage);
let blob = device.create_property_blob(rects.as_slice())?;
request.add_property(plane_id, property_id, blob.id())?;
Ok(Some(blob))
}
fn emit_color_props(
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
plane_id: u32,
want: Option<crate::Ycbcr>,
restate: bool,
) -> Result<usize, CoreError> {
let (Some(want), Some(props)) = (want, map.color.get(&plane_id)) else {
return Ok(0);
};
let resolved = props.resolve(want);
let committed = map
.color_committed
.get(&plane_id)
.filter(|_| !restate)
.copied()
.unwrap_or_default();
let mut written = 0;
for (now, before) in [
(resolved.encoding, committed.encoding),
(resolved.range, committed.range),
] {
if let Some((property_id, value)) = now
&& now != before
{
request.add_property(plane_id, property_id, value)?;
written += 1;
}
}
Ok(written)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FenceAction {
Arm {
property_id: u32,
},
CpuWait,
}
#[must_use]
pub const fn fence_action(property_id: Option<u32>) -> FenceAction {
match property_id {
Some(property_id) => FenceAction::Arm { property_id },
None => FenceAction::CpuWait,
}
}
pub fn arm_acquire_fences(
build: &mut crate::FrameBuild,
request: &mut drmkit_core::AtomicRequest,
map: &PlanePropertyMap,
real: bool,
) -> Result<(), CoreError> {
let mut armed = 0;
let mut waits = 0;
for acquisition in &build.acquisitions {
let Some(fence) = acquisition.buffer.acquire_fence.as_ref() else {
continue;
};
let Some(fd) = drmkit_sync::SyncFence::as_fd(fence) else {
continue;
};
let Some(entry) = build
.plan
.iter()
.find(|entry| entry.layer_id == acquisition.layer)
else {
continue;
};
match fence_action(map.property_id(entry.plane_id, PropTag::InFenceFd)) {
FenceAction::Arm { property_id } => {
let raw = std::os::fd::AsRawFd::as_raw_fd(&fd);
request.add_property(
entry.plane_id,
property_id,
u64::from(raw.cast_unsigned()),
)?;
armed += 1;
}
FenceAction::CpuWait => {
if !real {
continue;
}
waits += 1;
if let Err(error) = fence.wait(std::time::Duration::from_secs(1)) {
drmkit_log::log_warn!("acquire-fence CPU wait failed: {error}");
}
}
}
}
build.note_fences(armed, waits);
Ok(())
}