use drmkit_planes::{Allocator, LayerRef};
use drmkit_planes::{
Layer as PlaneLayer, LayerId, PlaneRegistry, PropTag, Rect, TestCommitter, TestFailure,
};
use std::collections::HashMap;
use drmkit_core::Device;
use drmkit_sync::SyncFence;
use crate::canvas::{CompositeCanvas, CompositeRect, CompositeSrc};
use crate::display::DisplayParams;
use crate::frame::{AcquireTally, CommitKind, FrameLifecycle, FrameOutcome, KernelResult};
use crate::lower::{LoweringInput, lower_layer};
use crate::release::{Acquisition, Released};
use crate::report::{CommitReport, LayerPlacement, Placement};
use crate::source::{BindingModel, LayerBufferSource, SourceError, SourceFormat};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct LayerHandle {
id: u32,
generation: u32,
}
impl LayerHandle {
#[must_use]
pub const fn is_valid(self) -> bool {
self.id != 0
}
#[must_use]
pub const fn layer_id(self) -> LayerId {
LayerId(((self.id as u64) << 32) | self.generation as u64)
}
}
impl From<LayerHandle> for LayerId {
fn from(handle: LayerHandle) -> Self {
handle.layer_id()
}
}
pub struct SceneLayer {
source: Box<dyn LayerBufferSource>,
display: DisplayParams,
content_type: drmkit_planes::ContentType,
update_hint_hz: u32,
app_priority: u8,
hints_dirty: bool,
pinned_plane: Option<u32>,
force_composited: bool,
identity_tag: Option<u64>,
display_dirty: bool,
last_fb_id: Option<u32>,
}
impl std::fmt::Debug for SceneLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SceneLayer")
.field("display", &self.display)
.field("content_type", &self.content_type)
.field("update_hint_hz", &self.update_hint_hz)
.field("app_priority", &self.app_priority)
.finish_non_exhaustive()
}
}
impl SceneLayer {
#[must_use]
pub fn source(&self) -> &dyn LayerBufferSource {
self.source.as_ref()
}
pub fn source_mut(&mut self) -> &mut dyn LayerBufferSource {
self.source.as_mut()
}
#[must_use]
pub const fn display(&self) -> &DisplayParams {
&self.display
}
pub const fn set_display(&mut self, display: DisplayParams) {
self.display = display;
self.display_dirty = true;
}
#[must_use]
pub const fn is_force_composited(&self) -> bool {
self.force_composited
}
pub const fn set_force_composited(&mut self, force: bool) {
self.force_composited = force;
self.hints_dirty = true;
}
#[must_use]
pub const fn identity_tag(&self) -> Option<u64> {
self.identity_tag
}
pub const fn set_identity_tag(&mut self, tag: Option<u64>) {
self.identity_tag = tag;
}
#[must_use]
pub const fn pinned_plane(&self) -> Option<u32> {
self.pinned_plane
}
pub const fn set_pinned_plane(&mut self, plane_id: Option<u32>) {
self.pinned_plane = plane_id;
self.hints_dirty = true;
}
#[must_use]
pub const fn content_type(&self) -> drmkit_planes::ContentType {
self.content_type
}
pub const fn set_content_type(&mut self, content_type: drmkit_planes::ContentType) {
self.content_type = content_type;
self.hints_dirty = true;
}
#[must_use]
pub const fn update_hint_hz(&self) -> u32 {
self.update_hint_hz
}
pub const fn set_update_hint(&mut self, hz: u32) {
self.update_hint_hz = hz;
self.hints_dirty = true;
}
#[must_use]
pub const fn app_priority(&self) -> u8 {
self.app_priority
}
pub const fn set_app_priority(&mut self, priority: u8) {
self.app_priority = priority;
self.hints_dirty = true;
}
#[must_use]
pub const fn hints_dirty(&self) -> bool {
self.hints_dirty
}
}
#[derive(Debug)]
enum Slot {
Occupied(Box<SceneLayer>),
Free,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct RebindReport {
pub incompatibilities: Vec<LayerIncompatibility>,
}
impl RebindReport {
#[must_use]
pub fn is_empty(&self) -> bool {
self.incompatibilities.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct LayerIncompatibility {
pub handle: LayerHandle,
pub reason: IncompatibilityReason,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum IncompatibilityReason {
DstRectOffScreen,
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct PlanePlan {
pub plane_id: u32,
pub layer_id: LayerId,
pub layer: drmkit_planes::Layer,
pub baseline: Option<drmkit_planes::PropertySnapshot>,
pub damage: Vec<crate::DamageRect>,
pub zpos: Option<u64>,
pub color: Option<crate::Ycbcr>,
}
struct Acquired {
starved: Vec<LayerId>,
acquisitions: Vec<Acquisition>,
plane_layers: Vec<(LayerId, PlaneLayer)>,
frame_damage: HashMap<LayerId, Vec<crate::DamageRect>>,
pins_unhonored: usize,
pinned: Vec<(LayerId, u32)>,
}
#[derive(Debug)]
pub struct FrameBuild {
pub(crate) acquisitions: Vec<Acquisition>,
pub(crate) plan: Vec<PlanePlan>,
disables: Vec<u32>,
report: CommitReport,
kind: CommitKind,
finalized: bool,
restate_sticky: bool,
}
impl FrameBuild {
#[must_use]
pub const fn report(&self) -> &CommitReport {
&self.report
}
pub(crate) const fn record_emission(
&mut self,
properties: usize,
framebuffers: usize,
damaged: usize,
) {
self.report.properties_written = properties;
self.report.fbs_attached = framebuffers;
self.report.damaged_layers = damaged;
}
#[must_use]
pub fn held(&self) -> usize {
self.acquisitions.len()
}
#[must_use]
pub fn plan(&self) -> &[PlanePlan] {
&self.plan
}
#[must_use]
pub const fn restates_sticky(&self) -> bool {
self.restate_sticky
}
pub(crate) fn note_fences(&mut self, armed: usize, cpu_waits: usize) {
self.report.in_fences_armed = armed;
self.report.in_fence_cpu_waits = cpu_waits;
}
#[must_use]
pub fn disables(&self) -> &[u32] {
&self.disables
}
}
impl Drop for FrameBuild {
fn drop(&mut self) {
if !self.finalized && !self.acquisitions.is_empty() && !std::thread::panicking() {
debug_assert!(
false,
"FrameBuild dropped without finalize_frame: {} acquisitions leaked",
self.acquisitions.len()
);
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SceneError {
#[error("scene is suspended")]
Suspended,
#[error("layer source failed: {0}")]
Source(#[from] SourceError),
#[error("lost DRM master during allocation")]
NotMaster,
#[error("planes from the previous CRTC are still lit; commit the detach first")]
DetachPending,
}
pub struct LayerScene {
crtc_id: u32,
slots: Vec<Slot>,
generations: Vec<u32>,
free: Vec<u32>,
allocator: Allocator,
canvas: Option<CompositeCanvas>,
lifecycle: FrameLifecycle,
retiring: Vec<(LayerId, Box<dyn LayerBufferSource>)>,
topology_dirty: bool,
committed_once: bool,
planes_unknown: bool,
last_canvas_plane: Option<u32>,
pin_verdicts: Vec<PinVerdict>,
detach: Option<PendingDetach>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct PendingDetach {
pub crtc_id: u32,
pub planes: Vec<u32>,
}
impl std::fmt::Debug for LayerScene {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("LayerScene")
.field("crtc_id", &self.crtc_id)
.field("layers", &self.len())
.field("suspended", &self.lifecycle.is_suspended())
.field("retiring_sources", &self.retiring.len())
.finish_non_exhaustive()
}
}
impl LayerScene {
#[must_use]
pub fn new(crtc_id: u32) -> Self {
Self {
crtc_id,
slots: Vec::new(),
generations: Vec::new(),
free: Vec::new(),
allocator: Allocator::new(),
canvas: None,
lifecycle: FrameLifecycle::new(),
retiring: Vec::new(),
topology_dirty: true,
committed_once: false,
planes_unknown: true,
last_canvas_plane: None,
pin_verdicts: Vec::new(),
detach: None,
}
}
#[must_use]
pub const fn crtc_id(&self) -> u32 {
self.crtc_id
}
#[must_use]
pub fn len(&self) -> usize {
self.slots
.iter()
.filter(|slot| matches!(slot, Slot::Occupied(_)))
.count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub const fn is_suspended(&self) -> bool {
self.lifecycle.is_suspended()
}
#[must_use]
pub const fn has_pending_flip(&self) -> bool {
self.lifecycle.has_pending_flip()
}
pub const fn flip_landed(&mut self) {
self.lifecycle.flip_landed();
}
pub fn resume(&mut self) {
self.lifecycle.resume();
self.planes_unknown = true;
self.last_canvas_plane = None;
self.pin_verdicts.clear();
}
pub fn on_session_paused(&mut self) {
self.drain();
for handle in self.handles().collect::<Vec<_>>() {
if let Some(layer) = self.layer_mut(handle) {
layer.source.on_session_paused();
}
}
if let Some(canvas) = self.canvas.as_mut() {
canvas.forget();
}
self.lifecycle.flip_landed();
}
pub fn on_session_resumed(&mut self, device: &Device) -> Result<(), SceneError> {
self.allocator.forget_output();
self.topology_dirty = true;
self.committed_once = false;
for handle in self.handles().collect::<Vec<_>>() {
if let Some(layer) = self.layer_mut(handle) {
layer.hints_dirty = true;
layer.display_dirty = true;
layer.last_fb_id = None;
layer.source.on_session_resumed(device)?;
}
}
if let Some(mut canvas) = self.canvas.take() {
canvas.forget();
match CompositeCanvas::create_in(
device,
canvas.width(),
canvas.height(),
canvas.fourcc(),
) {
Ok(fresh) => self.canvas = Some(fresh),
Err(error) => drmkit_log::log_warn!(
"scene: the composition canvas could not be made again after a resume ({error}); \
composition is off until it is enabled again"
),
}
}
self.resume();
Ok(())
}
pub fn add_layer(&mut self, source: Box<dyn LayerBufferSource>) -> LayerHandle {
self.topology_dirty = true;
let layer = Box::new(SceneLayer {
source,
display: DisplayParams::default(),
content_type: drmkit_planes::ContentType::Generic,
update_hint_hz: 0,
app_priority: 0,
hints_dirty: false,
pinned_plane: None,
force_composited: false,
identity_tag: None,
display_dirty: true,
last_fb_id: None,
});
if let Some(slot_index) = self.free.pop() {
let index = slot_index as usize - 1;
self.slots[index] = Slot::Occupied(layer);
return LayerHandle {
id: slot_index,
generation: self.generations[index],
};
}
self.slots.push(Slot::Occupied(layer));
self.generations.push(1);
let id = u32::try_from(self.slots.len()).unwrap_or(u32::MAX);
LayerHandle { id, generation: 1 }
}
pub fn remove_layer(&mut self, handle: LayerHandle) -> bool {
let Some(index) = self.resolve(handle) else {
return false;
};
let Slot::Occupied(mut layer) = std::mem::replace(&mut self.slots[index], Slot::Free)
else {
return false;
};
self.generations[index] = self.generations[index].wrapping_add(1);
self.free.push(handle.id);
layer.source.on_retired();
if self.lifecycle.buffers_in_flight() > 0 {
self.topology_dirty = true;
self.retiring.push((handle.layer_id(), layer.source));
}
self.allocator.forget_layer(handle.layer_id());
true
}
#[must_use]
pub fn layer(&self, handle: LayerHandle) -> Option<&SceneLayer> {
let index = self.resolve(handle)?;
match &self.slots[index] {
Slot::Occupied(layer) => Some(layer),
Slot::Free => None,
}
}
pub fn layer_mut(&mut self, handle: LayerHandle) -> Option<&mut SceneLayer> {
let index = self.resolve(handle)?;
match &mut self.slots[index] {
Slot::Occupied(layer) => Some(layer),
Slot::Free => None,
}
}
pub fn handles(&self) -> impl Iterator<Item = LayerHandle> + '_ {
self.slots
.iter()
.enumerate()
.filter_map(|(index, slot)| match slot {
Slot::Occupied(_) => Some(LayerHandle {
id: u32::try_from(index + 1).unwrap_or(u32::MAX),
generation: self.generations[index],
}),
Slot::Free => None,
})
}
fn resolve(&self, handle: LayerHandle) -> Option<usize> {
if !handle.is_valid() {
return None;
}
let index = handle.id as usize - 1;
if self.generations.get(index).copied()? != handle.generation {
return None;
}
Some(index)
}
fn acquire_every_layer(
&mut self,
registry: &PlaneRegistry,
crtc_index: u32,
tally: &mut AcquireTally,
) -> Result<Acquired, SceneError> {
let mut starved: Vec<LayerId> = Vec::new();
let mut acquisitions = Vec::new();
let mut plane_layers: Vec<(LayerId, PlaneLayer)> = Vec::new();
let mut frame_damage: HashMap<LayerId, Vec<crate::DamageRect>> = HashMap::new();
let mut pins_unhonored = 0usize;
let mut pinned: Vec<(LayerId, u32)> = Vec::new();
for handle in self.handles().collect::<Vec<_>>() {
let layer_id = handle.layer_id();
let Some(index) = self.resolve(handle) else {
continue;
};
let Slot::Occupied(layer) = &mut self.slots[index] else {
continue;
};
let acquired = match layer.source.acquire() {
Ok(buffer) => buffer,
Err(SourceError::WouldBlock) => {
tally.record(false);
let Some(fb_id) = layer.last_fb_id else {
continue;
};
starved.push(layer_id);
let (mut lowered, pin) =
lower(layer, fb_id, self.crtc_id, registry, crtc_index);
let pin = revoke_refused_pin(&self.pin_verdicts, layer_id, &mut lowered, pin);
match pin {
PinOutcome::Refused => pins_unhonored += 1,
PinOutcome::Honored(plane_id) => pinned.push((layer_id, plane_id)),
PinOutcome::NotRequested => {}
}
plane_layers.push((layer_id, lowered));
continue;
}
Err(other) => {
self.release_all(acquisitions);
return Err(SceneError::Source(other));
}
};
tally.record(true);
let (mut plane_layer, pin) =
lower(layer, acquired.fb_id, self.crtc_id, registry, crtc_index);
let pin = revoke_refused_pin(&self.pin_verdicts, layer_id, &mut plane_layer, pin);
match pin {
PinOutcome::Refused => pins_unhonored += 1,
PinOutcome::Honored(plane_id) => pinned.push((layer_id, plane_id)),
PinOutcome::NotRequested => {}
}
layer.last_fb_id = Some(acquired.fb_id);
plane_layers.push((layer_id, plane_layer));
let format = layer.source.format();
let damage = crate::commit::clip_damage(&acquired.damage, format.width, format.height);
if !damage.is_empty() {
frame_damage.insert(layer_id, damage);
}
acquisitions.push(Acquisition::new(layer_id, acquired));
}
Ok(Acquired {
starved,
acquisitions,
plane_layers,
frame_damage,
pins_unhonored,
pinned,
})
}
pub fn build_frame<C: TestCommitter>(
&mut self,
registry: &PlaneRegistry,
crtc_index: u32,
kind: CommitKind,
committer: &mut C,
) -> Result<FrameBuild, SceneError> {
if self.lifecycle.is_suspended() {
return Err(SceneError::Suspended);
}
if self.detach.is_some() {
return Err(SceneError::DetachPending);
}
if self.handles().any(|h| {
self.layer(h)
.is_some_and(super::scene::SceneLayer::hints_dirty)
}) {
self.allocator.invalidate_allocation();
}
let mut tally = AcquireTally::default();
let Acquired {
starved,
acquisitions,
plane_layers,
frame_damage,
pins_unhonored,
mut pinned,
} = self.acquire_every_layer(registry, crtc_index, &mut tally)?;
let mut plane_layers = plane_layers;
let (allocation, refused) = match self.allocate_frame(
&mut plane_layers,
&mut pinned,
registry,
crtc_index,
committer,
) {
Ok(allocated) => allocated,
Err(error) => {
self.release_all(acquisitions);
return Err(error);
}
};
let pins_unhonored = pins_unhonored + refused;
let canvas_plane = self.compose_unassigned(&allocation, registry, crtc_index);
self.last_canvas_plane = canvas_plane.as_ref().map(|c| c.plane_id);
let composited = canvas_plane.as_ref().map_or(0, |c| c.blended);
let report = build_report(
&tally,
&allocation,
registry,
&starved,
composited,
&pinned,
pins_unhonored,
);
for handle in self.handles().collect::<Vec<_>>() {
if let Some(layer) = self.layer_mut(handle) {
layer.hints_dirty = false;
layer.display_dirty = false;
}
}
self.topology_dirty = false;
self.committed_once = true;
let (mut plan, mut disables) = build_plan(
&self.allocator,
&allocation,
&plane_layers,
registry,
crtc_index,
&frame_damage,
&pinned,
);
self.disable_foreign(&mut disables, &plan, registry, crtc_index, committer);
self.request_colors(&mut plan);
if self.is_empty() {
disables.clear();
}
let canvas_id = canvas_plane
.as_ref()
.map(|composition| composition.plane_id);
let disables = if let Some(composition) = canvas_plane {
let plane_id = composition.plane_id;
plan.push(PlanePlan {
plane_id,
layer_id: LayerId(0),
layer: composition.layer,
baseline: None,
damage: Vec::new(),
zpos: None,
color: None,
});
disables.into_iter().filter(|id| *id != plane_id).collect()
} else {
disables
};
self.stack_plan(&mut plan, canvas_id, registry);
Ok(FrameBuild {
acquisitions,
plan,
disables,
report,
kind,
finalized: false,
restate_sticky: self.planes_unknown,
})
}
fn request_colors(&self, plan: &mut [PlanePlan]) {
for entry in plan {
entry.color = self
.handles()
.find(|handle| handle.layer_id() == entry.layer_id)
.and_then(|handle| self.layer(handle))
.map(|layer| layer.display().ycbcr());
}
}
fn disable_foreign<C: TestCommitter>(
&self,
disables: &mut Vec<u32>,
plan: &[PlanePlan],
registry: &PlaneRegistry,
crtc_index: u32,
committer: &mut C,
) {
if !self.planes_unknown || plan.is_empty() {
return;
}
let candidates: Vec<u32> = registry
.force_disable_candidates(crtc_index)
.map(|plane| plane.id)
.collect();
let lit = committer
.lit_planes(self.crtc_id)
.unwrap_or_else(|| candidates.clone());
for plane_id in lit {
if candidates.contains(&plane_id)
&& !disables.contains(&plane_id)
&& !plan.iter().any(|entry| entry.plane_id == plane_id)
{
disables.push(plane_id);
}
}
}
pub fn enable_composition(
&mut self,
device: &Device,
width: u32,
height: u32,
) -> Result<(), drmkit_dumb::DumbError> {
self.canvas = Some(CompositeCanvas::create(device, width, height)?);
Ok(())
}
pub fn enable_composition_on(
&mut self,
device: &Device,
width: u32,
height: u32,
plane: &drmkit_planes::PlaneCapabilities,
) -> Result<(), drmkit_dumb::DumbError> {
let fourcc = crate::canvas::canvas_format_for_plane(plane).ok_or(
drmkit_dumb::DumbError::InvalidConfig {
reason: "this plane scans out nothing the canvas can write",
},
)?;
self.canvas = Some(CompositeCanvas::create_in(device, width, height, fourcc)?);
Ok(())
}
#[must_use]
pub fn canvas_fourcc(&self) -> Option<u32> {
self.canvas.as_ref().map(CompositeCanvas::fourcc)
}
fn allocate_with_canvas<C: TestCommitter>(
&mut self,
refs: &[LayerRef<'_>],
registry: &PlaneRegistry,
crtc_index: u32,
committer: &mut C,
) -> Result<drmkit_planes::Allocation, SceneError> {
let reserved = canvas_reservation(
self.canvas.is_some(),
refs,
registry,
crtc_index,
self.last_canvas_plane,
);
self.allocator.set_reserved_planes(&reserved);
self.allocator.hold_canvas_plane(reserved.first().copied());
let hosts: Vec<u32> = self.canvas.as_ref().map_or_else(Vec::new, |canvas| {
registry
.force_disable_candidates(crtc_index)
.filter(|plane| plane.scans_out_linear(canvas.fourcc()))
.map(|plane| plane.id)
.collect()
});
let canvas_layer = self
.canvas
.as_ref()
.and_then(|canvas| lower_canvas(canvas, self.crtc_id, self.canvas_zpos()));
self.allocator.set_canvas(&hosts, canvas_layer.as_ref());
let mut allocation = match self
.allocator
.allocate(refs, registry, crtc_index, committer)
{
Ok(allocation) => allocation,
Err(TestFailure::NotMaster) => return Err(SceneError::NotMaster),
Err(TestFailure::Rejected) => drmkit_planes::Allocation::default(),
};
if self.canvas.is_none()
|| allocation.composited.is_empty()
|| self.allocator.canvas_plane().is_some()
|| drmkit_planes::stacks_by_plane_id(registry, crtc_index)
{
return Ok(allocation);
}
let Some(spare) = canvas_plane_order(&hosts, registry, crtc_index, self.last_canvas_plane)
.find(|id| {
allocation.assignment.get(*id).is_none() && !is_multirect_virtual(registry, *id)
})
else {
return Ok(allocation);
};
let first_pass_tests = allocation.diagnostics.test_commits_issued;
let first_pass_budget = allocation.diagnostics.budget_exhausted;
self.allocator.set_reserved_planes(&[spare]);
self.allocator.hold_canvas_plane(Some(spare));
let outcome = self
.allocator
.allocate(refs, registry, crtc_index, committer);
match outcome {
Ok(mut second) => {
second.diagnostics.test_commits_issued += first_pass_tests;
second.diagnostics.budget_exhausted |= first_pass_budget;
allocation = second;
}
Err(TestFailure::NotMaster) => return Err(SceneError::NotMaster),
Err(TestFailure::Rejected) => {}
}
Ok(allocation)
}
fn allocate_frame<C: TestCommitter>(
&mut self,
plane_layers: &mut [(LayerId, PlaneLayer)],
pinned: &mut Vec<(LayerId, u32)>,
registry: &PlaneRegistry,
crtc_index: u32,
committer: &mut C,
) -> Result<(drmkit_planes::Allocation, usize), SceneError> {
lower_bottom_layer_to_primary_slot(plane_layers, registry, crtc_index);
let refs: Vec<LayerRef<'_>> = plane_layers
.iter()
.map(|(id, layer)| LayerRef { id: *id, layer })
.collect();
let mut allocation = self.allocate_with_canvas(&refs, registry, crtc_index, committer)?;
drop(refs);
let refused = self.test_pins(&mut allocation, plane_layers, pinned, committer)?;
Ok((allocation, refused))
}
fn test_pins<C: TestCommitter>(
&mut self,
allocation: &mut drmkit_planes::Allocation,
plane_layers: &mut [(LayerId, PlaneLayer)],
pinned: &mut Vec<(LayerId, u32)>,
committer: &mut C,
) -> Result<usize, SceneError> {
let (refused, tests) = self.ask_about_pins(allocation, plane_layers, pinned, committer)?;
allocation.diagnostics.test_commits_issued += tests;
if refused.is_empty() {
return Ok(0);
}
for (layer_id, plane_layer) in plane_layers.iter_mut() {
if refused.contains(layer_id) {
plane_layer.set_pinned(false).set_assigned_plane(None);
}
}
pinned.retain(|(layer_id, _)| !refused.contains(layer_id));
allocation.composited.extend(refused.iter().copied());
self.allocator.invalidate_allocation();
Ok(refused.len())
}
fn ask_about_pins<C: TestCommitter>(
&mut self,
allocation: &drmkit_planes::Allocation,
plane_layers: &[(LayerId, PlaneLayer)],
pinned: &[(LayerId, u32)],
committer: &mut C,
) -> Result<(Vec<LayerId>, usize), SceneError> {
let layer_of = |id: LayerId| {
plane_layers
.iter()
.find(|(layer_id, _)| *layer_id == id)
.map(|(_, layer)| layer)
};
let mut pairs: Vec<(u32, LayerRef<'_>)> = allocation
.assignment
.entries()
.iter()
.filter_map(|(plane_id, id)| {
layer_of(*id).map(|layer| (*plane_id, LayerRef { id: *id, layer }))
})
.collect();
let mut untested = Vec::new();
for (id, plane_id) in pinned {
let Some(layer) = layer_of(*id) else {
continue;
};
match pin_verdict(&self.pin_verdicts, *id, *plane_id, layer.property_hash()) {
Some(true) => pairs.push((*plane_id, LayerRef { id: *id, layer })),
Some(false) => {}
None => untested.push((*id, *plane_id, layer)),
}
}
let mut refused = Vec::new();
let mut tests = 0;
for (id, plane_id, layer) in untested {
pairs.push((plane_id, LayerRef { id, layer }));
tests += 1;
let accepted = match committer.test_assignment(&pairs) {
Ok(()) => true,
Err(TestFailure::NotMaster) => return Err(SceneError::NotMaster),
Err(TestFailure::Rejected) => false,
};
remember_pin(
&mut self.pin_verdicts,
PinVerdict {
layer: id,
plane: plane_id,
hash: layer.property_hash(),
accepted,
},
);
if !accepted {
pairs.pop();
refused.push(id);
}
}
Ok((refused, tests))
}
fn stack_plan(&self, plan: &mut [PlanePlan], canvas: Option<u32>, registry: &PlaneRegistry) {
let armed = |plan: &[PlanePlan]| -> Vec<(u32, Option<u64>)> {
plan.iter()
.map(|entry| (entry.plane_id, entry.layer.property(PropTag::Zpos)))
.collect()
};
let slot = self.allocator.canvas_zpos();
let slotted = canvas
.filter(|_| slot.is_some())
.and_then(|canvas| drmkit_planes::stacked_zpos_beneath(registry, &armed(plan), canvas));
let stack = slotted.unwrap_or_else(|| {
if let Some(canvas) = canvas
&& slot.is_some()
&& let Some(entry) = plan.iter_mut().find(|entry| entry.plane_id == canvas)
{
entry.layer.set_property(PropTag::Zpos, self.canvas_zpos());
}
drmkit_planes::stacked_zpos(registry, &armed(plan))
});
for (plane_id, zpos) in stack {
if let Some(entry) = plan.iter_mut().find(|entry| entry.plane_id == plane_id) {
entry.zpos = Some(zpos);
}
}
}
fn canvas_zpos(&self) -> u64 {
let mut zpos = 0u64;
for handle in self.handles().collect::<Vec<_>>() {
if let Some(layer) = self.layer(handle) {
zpos = zpos.max(layer.display().zpos.unwrap_or(0));
}
}
zpos.saturating_add(1)
}
fn compose_unassigned(
&mut self,
allocation: &drmkit_planes::Allocation,
registry: &PlaneRegistry,
crtc_index: u32,
) -> Option<Composition> {
if allocation.composited.is_empty() {
return None;
}
let free = |id: &u32| {
allocation.assignment.get(*id).is_none()
&& registry.by_id(*id).is_none_or(|plane| {
drmkit_planes::multirect_pairing_ok(plane.multirect_parent, |parent| {
allocation.assignment.get(parent).is_some()
})
})
};
let plane_id = if drmkit_planes::stacks_by_plane_id(registry, crtc_index) {
self.allocator.canvas_plane().filter(free).or_else(|| {
registry
.force_disable_candidates(crtc_index)
.map(|plane| plane.id)
.filter(free)
.max()
})?
} else {
let hosts: Vec<u32> = registry
.force_disable_candidates(crtc_index)
.filter(|plane| {
self.canvas
.as_ref()
.is_some_and(|canvas| plane.scans_out_linear(canvas.fourcc()))
})
.map(|plane| plane.id)
.collect();
self.allocator.canvas_plane().filter(free).or_else(|| {
canvas_plane_order(&hosts, registry, crtc_index, self.last_canvas_plane)
.find(|id| free(id))
})?
};
let mut targets: Vec<(u64, usize)> = Vec::new();
for handle in self.handles().collect::<Vec<_>>() {
let id = handle.layer_id();
if !allocation.composited.contains(&id) {
continue;
}
let Some(index) = self.resolve(handle) else {
continue;
};
let order = self
.layer(handle)
.and_then(|l| l.display().zpos)
.unwrap_or(0);
targets.push((order, index));
}
if targets.is_empty() {
return None;
}
targets.sort_unstable();
let zpos = self
.allocator
.canvas_zpos()
.unwrap_or_else(|| self.canvas_zpos());
let Self {
slots,
canvas,
crtc_id,
..
} = self;
let canvas = canvas.as_mut()?;
canvas.begin_frame();
canvas.clear();
let blended = blend_targets(canvas, slots, &targets);
if blended == 0 {
return None;
}
canvas.flush();
Some(Composition {
plane_id,
layer: lower_canvas(canvas, *crtc_id, zpos)?,
blended,
})
}
pub fn finalize_frame(&mut self, build: FrameBuild, result: KernelResult) -> CommitReport {
self.finalize_frame_with_fence(build, result, None)
}
pub fn finalize_frame_with_fence(
&mut self,
mut build: FrameBuild,
result: KernelResult,
release_fence: Option<&SyncFence>,
) -> CommitReport {
build.finalized = true;
let acquisitions = std::mem::take(&mut build.acquisitions);
let report = std::mem::take(&mut build.report);
let kind = build.kind;
let plan = std::mem::take(&mut build.plan);
if matches!(kind, CommitKind::Real { .. }) && matches!(result, KernelResult::Ok) {
let applied: Vec<(u32, drmkit_planes::LayerRef<'_>)> = plan
.iter()
.map(|entry| {
(
entry.plane_id,
drmkit_planes::LayerRef {
id: entry.layer_id,
layer: &entry.layer,
},
)
})
.collect();
let stacked: Vec<(u32, u64)> = plan
.iter()
.filter_map(|entry| entry.zpos.map(|zpos| (entry.plane_id, zpos)))
.collect();
self.allocator.record_commit_stacked(&applied, &stacked);
if !plan.is_empty() {
self.planes_unknown = false;
}
}
let wanting: Vec<LayerId> = if release_fence.is_some() {
self.handles()
.filter(|handle| {
self.layer(*handle)
.is_some_and(|layer| layer.source().wants_release_fence())
})
.map(LayerHandle::layer_id)
.collect()
} else {
Vec::new()
};
let wants_fence = |layer: LayerId| wanting.contains(&layer);
let outcome: FrameOutcome = self.lifecycle.finalize(
kind,
result,
acquisitions,
report,
release_fence,
wants_fence,
);
if outcome.invalidate_allocation {
self.allocator.invalidate_allocation();
}
self.deliver(outcome.released);
outcome.report
}
pub const fn set_force_full_property_writes(&mut self, force: bool) {
self.allocator.set_force_full_property_writes(force);
}
#[must_use]
pub const fn force_full_property_writes(&self) -> bool {
self.allocator.force_full_property_writes()
}
#[must_use]
pub fn content_changed(&self) -> bool {
if !self.committed_once || self.topology_dirty {
return true;
}
self.handles().any(|handle| {
self.layer(handle).is_some_and(|layer| {
layer.hints_dirty() || layer.display_dirty || layer.source().has_fresh_content()
})
})
}
#[must_use]
pub fn find_by_identity_tag(&self, tag: u64) -> Option<LayerHandle> {
self.handles().find(|handle| {
self.layer(*handle)
.is_some_and(|layer| layer.identity_tag() == Some(tag))
})
}
#[must_use]
pub const fn pending_detach(&self) -> Option<&PendingDetach> {
self.detach.as_ref()
}
pub fn detach_committed(&mut self) {
self.detach = None;
}
pub fn rebind(&mut self, crtc_id: u32, width: u32, height: u32) -> RebindReport {
if crtc_id != self.crtc_id {
let planes = self.allocator.lit_planes();
if self.detach.is_none() && !planes.is_empty() {
self.detach = Some(PendingDetach {
crtc_id: self.crtc_id,
planes,
});
}
}
self.crtc_id = crtc_id;
self.allocator.forget_output();
self.topology_dirty = true;
self.committed_once = false;
self.planes_unknown = true;
self.last_canvas_plane = None;
self.pin_verdicts.clear();
for handle in self.handles().collect::<Vec<_>>() {
if let Some(layer) = self.layer_mut(handle) {
layer.hints_dirty = true;
layer.display_dirty = true;
layer.last_fb_id = None;
}
}
let incompatibilities = self
.handles()
.filter_map(|handle| {
let layer = self.layer(handle)?;
let dst = layer.display().dst_rect;
let off_screen = dst.x < 0
|| dst.y < 0
|| dst.x.saturating_add(dst.w.cast_signed()) > width.cast_signed()
|| dst.y.saturating_add(dst.h.cast_signed()) > height.cast_signed();
off_screen.then_some(LayerIncompatibility {
handle,
reason: IncompatibilityReason::DstRectOffScreen,
})
})
.collect();
RebindReport { incompatibilities }
}
#[must_use]
pub fn wants_release_fence(&self) -> bool {
self.handles().any(|handle| {
self.layer(handle)
.is_some_and(|layer| layer.source().wants_release_fence())
})
}
pub fn release_displaced(&mut self, commit_seq: u64) {
let released = self.lifecycle.release_displaced(commit_seq);
self.deliver(released);
}
pub fn drain(&mut self) {
let released = self.lifecycle.drain();
self.deliver(released);
}
fn deliver(&mut self, released: Released) {
for acquisition in released.acquisitions {
let layer_id = acquisition.layer;
let fence = acquisition.release_fence;
let live = self.handles().find(|h| h.layer_id() == layer_id);
if let Some(handle) = live
&& let Some(layer) = self.layer_mut(handle)
{
layer.source.release_with_fence(acquisition.buffer, fence);
continue;
}
if let Some(entry) = self.retiring.iter_mut().find(|(id, _)| *id == layer_id) {
entry.1.release_with_fence(acquisition.buffer, fence);
}
}
if self.lifecycle.buffers_in_flight() == 0 {
self.retiring.clear();
}
}
fn release_all(&mut self, acquisitions: Vec<Acquisition>) {
self.deliver(Released {
acquisitions,
reason: crate::release::ReleaseReason::CommitFailed,
});
}
}
fn build_report(
tally: &AcquireTally,
allocation: &drmkit_planes::Allocation,
registry: &PlaneRegistry,
starved: &[LayerId],
composited: usize,
pinned: &[(LayerId, u32)],
pins_unhonored: usize,
) -> CommitReport {
let starved_assigned = starved
.iter()
.filter(|layer| allocation.assignment.get_plane_of(**layer).is_some())
.count();
let placements = allocation
.assignment
.entries()
.iter()
.map(|(plane_id, layer)| LayerPlacement {
layer: *layer,
placement: Placement::AssignedToPlane,
plane_id: Some(*plane_id),
plane_rotation_bits: registry
.by_id(*plane_id)
.map_or(0, |plane| plane.rotation_bits),
})
.chain(allocation.composited.iter().map(|layer| LayerPlacement {
layer: *layer,
placement: Placement::Unassigned,
plane_id: None,
plane_rotation_bits: 0,
}))
.chain(pinned.iter().map(|(layer, plane_id)| {
LayerPlacement {
layer: *layer,
placement: Placement::AssignedToPlane,
plane_id: Some(*plane_id),
plane_rotation_bits: registry
.by_id(*plane_id)
.map_or(0, |plane| plane.rotation_bits),
}
}))
.collect();
CommitReport {
layers_total: tally.considered(),
layers_assigned: allocation.assignment.len() - starved_assigned + pinned.len(),
pin_requests_unhonored: pins_unhonored,
layers_composited: composited,
layers_unassigned: allocation.composited.len() - composited,
layers_skipped_no_frame: tally.skipped_no_frame,
test_commits_issued: allocation.diagnostics.test_commits_issued,
fb_delta_fast_path: allocation.diagnostics.fb_delta_fast_path,
budget_exhausted: allocation.diagnostics.budget_exhausted,
placements,
..CommitReport::default()
}
}
struct Composition {
plane_id: u32,
layer: PlaneLayer,
blended: usize,
}
pub(crate) fn canvas_reservation(
has_canvas: bool,
refs: &[LayerRef<'_>],
registry: &PlaneRegistry,
crtc_index: u32,
previous: Option<u32>,
) -> Vec<u32> {
if !has_canvas || drmkit_planes::stacks_by_plane_id(registry, crtc_index) {
return Vec::new();
}
let candidates: Vec<&drmkit_planes::PlaneCapabilities> =
registry.force_disable_candidates(crtc_index).collect();
if candidates.is_empty() {
return Vec::new();
}
if !refs.is_empty()
&& let Some(primary) = candidates
.iter()
.find(|plane| plane.plane_type == drmkit_planes::PlaneType::Primary)
&& let Some(pin) = primary.zpos_min
&& crate::canvas_format_for_plane(primary).is_some()
{
let zposes: Vec<Option<u64>> = refs
.iter()
.map(|layer| layer.layer.property(drmkit_planes::PropTag::Zpos))
.collect();
let anyone_eligible = zposes
.iter()
.any(|zpos| zpos.is_none_or(|zpos| zpos == pin))
|| (drmkit_planes::zpos_fixed(primary) && bottom_slot_layer(&zposes, pin).is_some());
if !anyone_eligible {
return vec![primary.id];
}
}
let hosts: Vec<u32> = candidates
.iter()
.filter(|plane| crate::canvas_format_for_plane(plane).is_some())
.map(|plane| plane.id)
.collect();
if refs.len() > hosts.len() {
canvas_plane_order(&hosts, registry, crtc_index, previous)
.find(|id| !is_multirect_virtual(registry, *id))
.into_iter()
.collect()
} else {
Vec::new()
}
}
fn is_multirect_virtual(registry: &PlaneRegistry, plane_id: u32) -> bool {
registry
.by_id(plane_id)
.is_some_and(|plane| plane.multirect_parent.is_some())
}
pub(crate) fn canvas_plane_order<'a>(
hosts: &'a [u32],
registry: &'a PlaneRegistry,
crtc_index: u32,
previous: Option<u32>,
) -> impl Iterator<Item = u32> + 'a {
let of_type = move |plane_type: drmkit_planes::PlaneType| {
registry
.for_crtc(crtc_index)
.filter(move |plane| {
plane.plane_type == plane_type
&& hosts.contains(&plane.id)
&& previous != Some(plane.id)
})
.map(|plane| plane.id)
};
previous
.filter(|id| hosts.contains(id))
.into_iter()
.chain(of_type(drmkit_planes::PlaneType::Overlay))
.chain(of_type(drmkit_planes::PlaneType::Primary))
}
pub(crate) const fn round_16_16(value: u32) -> u32 {
(value >> 16) + ((value >> 15) & 1)
}
fn blend_targets(
canvas: &mut CompositeCanvas,
slots: &mut [Slot],
targets: &[(u64, usize)],
) -> usize {
let mut blended = 0;
for (_, index) in targets {
let Slot::Occupied(layer) = &mut slots[*index] else {
continue;
};
let display = layer.display;
let format = layer.source.format();
if !crate::format_supported(format.fourcc) {
continue;
}
let src_rect = display
.src_rect_fixed
.map_or(display.src_rect, |fixed| Rect {
x: round_16_16(fixed.x).cast_signed(),
y: round_16_16(fixed.y).cast_signed(),
w: round_16_16(fixed.w),
h: round_16_16(fixed.h),
});
let blend = &mut |mapping: &mut drmkit_dumb::Mapping<'_>| {
let src = CompositeSrc {
pixels: mapping.pixels(),
src_stride_bytes: mapping.stride(),
src_width: mapping.width(),
src_height: mapping.height(),
drm_fourcc: format.fourcc,
plane_alpha: display.alpha.unwrap_or(u16::MAX),
};
canvas.blend(
&src,
CompositeRect {
x: src_rect.x,
y: src_rect.y,
w: if src_rect.w == 0 {
format.width
} else {
src_rect.w
},
h: if src_rect.h == 0 {
format.height
} else {
src_rect.h
},
},
CompositeRect {
x: display.dst_rect.x,
y: display.dst_rect.y,
w: display.dst_rect.w,
h: display.dst_rect.h,
},
);
};
if layer
.source
.with_mapping(drmkit_dumb::MapAccess::Read, blend)
.is_err()
{
continue;
}
blended += 1;
}
blended
}
pub(crate) fn bottom_slot_layer(zposes: &[Option<u64>], pin: u64) -> Option<usize> {
let all: Option<Vec<u64>> = zposes.iter().copied().collect();
let all = all?;
let lowest = *all.iter().min()?;
let mut at_lowest = all.iter().enumerate().filter(|(_, z)| **z == lowest);
let (index, _) = at_lowest.next()?;
if at_lowest.next().is_some() {
return None;
}
all.iter()
.enumerate()
.all(|(i, z)| i == index || *z > pin)
.then_some(index)
}
fn lower_bottom_layer_to_primary_slot(
plane_layers: &mut [(LayerId, PlaneLayer)],
registry: &PlaneRegistry,
crtc_index: u32,
) {
let Some(primary) = registry.for_crtc(crtc_index).find(|plane| {
plane.plane_type == drmkit_planes::PlaneType::Primary && drmkit_planes::zpos_fixed(plane)
}) else {
return;
};
let Some(pin) = primary.zpos_min else {
return;
};
let zposes: Vec<Option<u64>> = plane_layers
.iter()
.map(|(_, layer)| layer.property(PropTag::Zpos))
.collect();
if let Some(index) = bottom_slot_layer(&zposes, pin) {
plane_layers[index].1.set_property(PropTag::Zpos, pin);
}
}
fn lower_canvas(canvas: &CompositeCanvas, crtc_id: u32, zpos: u64) -> Option<PlaneLayer> {
let fb_id = canvas.fb_id()?;
let (w, h) = (canvas.width(), canvas.height());
let mut plane_layer = PlaneLayer::new();
lower_layer(
&LoweringInput {
display: DisplayParams {
src_rect: Rect { x: 0, y: 0, w, h },
dst_rect: Rect { x: 0, y: 0, w, h },
zpos: Some(zpos),
..DisplayParams::default()
},
format: SourceFormat {
fourcc: drmkit_fmt::fourcc::ARGB8888,
modifier: 0,
width: w,
height: h,
},
binding: BindingModel::SceneSubmitsFbId,
fb_id,
crtc_id,
default_zpos: None,
},
&mut plane_layer,
);
Some(plane_layer)
}
fn lower(
layer: &SceneLayer,
fb_id: u32,
crtc_id: u32,
registry: &PlaneRegistry,
crtc_index: u32,
) -> (drmkit_planes::Layer, PinOutcome) {
let mut plane_layer = PlaneLayer::new();
lower_layer(
&LoweringInput {
display: layer.display,
format: layer.source.format(),
binding: layer.source.binding_model(),
fb_id,
crtc_id,
default_zpos: None,
},
&mut plane_layer,
);
plane_layer.set_content_type(layer.content_type);
plane_layer.set_update_hint(layer.update_hint_hz);
plane_layer.set_app_priority(layer.app_priority);
plane_layer.set_force_composited(layer.force_composited);
let pin = match layer.pinned_plane {
None => PinOutcome::NotRequested,
Some(plane_id) if pin_is_honorable(&plane_layer, plane_id, registry, crtc_index) => {
plane_layer.set_pinned(true);
plane_layer.set_assigned_plane(Some(plane_id));
PinOutcome::Honored(plane_id)
}
Some(_) => PinOutcome::Refused,
};
(plane_layer, pin)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PinOutcome {
NotRequested,
Honored(u32),
Refused,
}
#[derive(Debug, Clone, Copy)]
struct PinVerdict {
layer: LayerId,
plane: u32,
hash: u64,
accepted: bool,
}
fn pin_verdict(verdicts: &[PinVerdict], layer: LayerId, plane: u32, hash: u64) -> Option<bool> {
verdicts
.iter()
.find(|v| v.layer == layer && v.plane == plane && v.hash == hash)
.map(|v| v.accepted)
}
fn remember_pin(verdicts: &mut Vec<PinVerdict>, verdict: PinVerdict) {
const MAX_PIN_VERDICTS: usize = 64;
if verdicts.len() >= MAX_PIN_VERDICTS {
verdicts.clear();
}
verdicts.push(verdict);
}
fn revoke_refused_pin(
verdicts: &[PinVerdict],
layer_id: LayerId,
plane_layer: &mut PlaneLayer,
pin: PinOutcome,
) -> PinOutcome {
match pin {
PinOutcome::Honored(plane_id)
if pin_verdict(verdicts, layer_id, plane_id, plane_layer.property_hash())
== Some(false) =>
{
plane_layer.set_pinned(false).set_assigned_plane(None);
PinOutcome::Refused
}
other => other,
}
}
fn pin_is_honorable(
layer: &PlaneLayer,
plane_id: u32,
registry: &PlaneRegistry,
crtc_index: u32,
) -> bool {
let Some(plane) = registry.by_id(plane_id) else {
return false;
};
if !plane.compatible_with_crtc(crtc_index) {
return false;
}
if plane.plane_type == drmkit_planes::PlaneType::Cursor {
return false;
}
layer
.property(PropTag::PixelFormat)
.and_then(|fourcc| u32::try_from(fourcc).ok())
.is_none_or(|fourcc| plane.scans_out(fourcc, drmkit_fmt::Modifier(layer.modifier())))
}
fn build_plan(
allocator: &drmkit_planes::Allocator,
allocation: &drmkit_planes::Allocation,
plane_layers: &[(LayerId, drmkit_planes::Layer)],
registry: &PlaneRegistry,
crtc_index: u32,
damage: &HashMap<LayerId, Vec<crate::DamageRect>>,
pinned: &[(LayerId, u32)],
) -> (Vec<PlanePlan>, Vec<u32>) {
let plan: Vec<PlanePlan> = plane_layers
.iter()
.filter_map(|(id, layer)| {
allocation
.assignment
.get_plane_of(*id)
.or_else(|| {
pinned
.iter()
.find(|(layer_id, _)| layer_id == id)
.map(|(_, plane_id)| *plane_id)
})
.map(|plane_id| PlanePlan {
plane_id,
layer_id: *id,
layer: layer.clone(),
baseline: allocator.committed_baseline(plane_id, *id).copied(),
damage: damage.get(id).cloned().unwrap_or_default(),
zpos: None,
color: None,
})
})
.collect();
let disables = registry
.force_disable_candidates(crtc_index)
.map(|plane| plane.id)
.filter(|plane_id| {
!plan.iter().any(|entry| entry.plane_id == *plane_id)
&& !allocator.plane_is_off(*plane_id)
})
.collect();
(plan, disables)
}
impl Drop for LayerScene {
fn drop(&mut self) {
if self.lifecycle.has_pending_flip() && !std::thread::panicking() {
debug_assert!(
false,
"LayerScene dropped with a page-flip event still armed: dispatch \
the event (flip_landed) first (invariant 5)"
);
}
}
}