use drmkit_fmt::fourcc;
use drmkit_planes::{
Allocator, ContentType, Layer, LayerId, LayerRef, PlaneCapabilities, PlaneRegistry, PlaneType,
PropTag, TestCommitter, TestFailure,
};
fn plane(id: u32, plane_type: PlaneType, zpos: Option<(u64, u64)>) -> PlaneCapabilities {
PlaneCapabilities {
id,
possible_crtcs: 0b1,
plane_type,
formats: vec![fourcc::XRGB8888, fourcc::ARGB8888],
zpos_min: zpos.map(|(min, _)| min),
zpos_max: zpos.map(|(_, max)| max),
supports_scaling: true,
..PlaneCapabilities::default()
}
}
fn registry() -> PlaneRegistry {
PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, Some((0, 0))),
plane(32, PlaneType::Overlay, Some((1, 4))),
plane(33, PlaneType::Overlay, Some((1, 4))),
])
}
fn layer_at(x: i32, y: i32, w: u32, h: u32, zpos: u64) -> Layer {
let mut layer = Layer::new();
layer
.set_property(PropTag::PixelFormat, u64::from(fourcc::XRGB8888))
.set_property(PropTag::FbId, 1)
.set_property(PropTag::CrtcX, u64::from(x.cast_unsigned()))
.set_property(PropTag::CrtcY, u64::from(y.cast_unsigned()))
.set_property(PropTag::CrtcW, u64::from(w))
.set_property(PropTag::CrtcH, u64::from(h))
.set_property(PropTag::SrcW, u64::from(w) << 16)
.set_property(PropTag::SrcH, u64::from(h) << 16)
.set_property(PropTag::Zpos, zpos);
layer
}
#[derive(Debug, Default)]
struct Committer {
calls: usize,
reject_first: usize,
max_planes: Option<usize>,
fail_with: Option<TestFailure>,
seen: Vec<Vec<u32>>,
extra: Option<u32>,
extras_seen: Vec<Option<u32>>,
count_extra: bool,
multirect: Vec<(u32, u32)>,
orphans_tested: usize,
}
impl TestCommitter for Committer {
fn test_assignment(&mut self, assignment: &[(u32, LayerRef<'_>)]) -> Result<(), TestFailure> {
self.calls += 1;
let mut planes: Vec<u32> = assignment.iter().map(|(id, _)| *id).collect();
planes.sort_unstable();
self.seen.push(planes.clone());
self.extras_seen.push(self.extra);
let armed = assignment.len() + usize::from(self.count_extra && self.extra.is_some());
let orphaned = self
.multirect
.iter()
.any(|(child, parent)| planes.contains(child) && !planes.contains(parent));
self.orphans_tested += usize::from(orphaned);
if let Some(failure) = self.fail_with {
return Err(failure);
}
if self.calls <= self.reject_first {
return Err(TestFailure::Rejected);
}
if orphaned || self.max_planes.is_some_and(|max| armed > max) {
return Err(TestFailure::Rejected);
}
Ok(())
}
fn set_extra_plane(&mut self, plane_id: u32, _layer: Layer) {
self.extra = Some(plane_id);
}
fn clear_extra_plane(&mut self) {
self.extra = None;
}
}
fn refs(layers: &[(LayerId, Layer)]) -> Vec<LayerRef<'_>> {
layers
.iter()
.map(|(id, layer)| LayerRef { id: *id, layer })
.collect()
}
#[test]
fn places_every_layer_when_planes_are_plentiful() {
let layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(result.assignment.len(), 2);
assert!(result.composited.is_empty());
assert_eq!(
result.diagnostics.test_commits_issued, 1,
"preseed passes first try"
);
assert!(!result.diagnostics.fb_delta_fast_path);
}
#[test]
fn a_layer_with_no_format_is_never_placed() {
let mut bare = Layer::new();
bare.set_property(PropTag::CrtcW, 100)
.set_property(PropTag::CrtcH, 100);
let layers = vec![(LayerId(1), bare)];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert!(result.assignment.is_empty());
assert_eq!(result.composited, vec![LayerId(1)]);
}
#[test]
fn force_composited_layers_are_routed_to_composition() {
let mut composited = layer_at(0, 0, 640, 480, 1);
composited.set_force_composited(true);
let layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), composited),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(result.assignment.len(), 1);
assert_eq!(result.composited, vec![LayerId(2)]);
}
#[test]
fn transient_composited_layers_are_routed_to_composition() {
let mut transient = layer_at(0, 0, 640, 480, 1);
transient.set_transient_composited(true);
let layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), transient),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let first = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(first.assignment.len(), 1);
assert_eq!(first.composited, vec![LayerId(2)]);
let next = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(next.composited, vec![LayerId(2)], "and on the next frame");
}
#[test]
fn an_unplaced_composition_layer_is_not_reported_for_composition() {
let mut canvas = Layer::new();
canvas
.set_property(PropTag::CrtcW, 1920)
.set_property(PropTag::CrtcH, 1080);
canvas.set_composition_layer(true);
let layers = vec![(LayerId(1), canvas)];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert!(result.assignment.is_empty(), "no format, so no plane");
assert!(
result.composited.is_empty(),
"the canvas is never blended onto itself: {:?}",
result.composited
);
}
#[test]
fn externally_bound_and_pinned_layers_are_skipped_entirely() {
let mut bound = layer_at(0, 0, 640, 480, 1);
bound.set_externally_bound(true);
let mut pinned = layer_at(0, 0, 320, 240, 2);
pinned.set_pinned(true);
let layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), bound),
(LayerId(3), pinned),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(result.assignment.len(), 1);
assert!(
result.composited.is_empty(),
"the scene owns these layers; the allocator neither places nor composites them"
);
}
#[test]
fn backtracking_drops_the_lowest_priority_layer() {
let mut video = layer_at(0, 0, 1920, 1080, 1);
video.set_content_type(ContentType::Video);
let ui = layer_at(0, 0, 1920, 1080, 2);
let layers = vec![(LayerId(1), video), (LayerId(2), ui)];
let mut allocator = Allocator::new();
let mut committer = Committer {
max_planes: Some(1),
..Committer::default()
};
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert_eq!(result.assignment.len(), 1);
assert_eq!(
result.assignment.entries()[0].1,
LayerId(1),
"the Video layer must survive; the Generic one is dropped"
);
assert_eq!(result.composited, vec![LayerId(2)]);
}
#[test]
fn lost_master_propagates_rather_than_retrying() {
let layers = vec![(LayerId(1), layer_at(0, 0, 1920, 1080, 0))];
let mut allocator = Allocator::new();
let mut committer = Committer {
fail_with: Some(TestFailure::NotMaster),
..Committer::default()
};
assert!(matches!(
allocator.allocate(&refs(&layers), ®istry(), 0, &mut committer),
Err(TestFailure::NotMaster)
));
assert_eq!(
committer.calls, 1,
"no smaller assignment can help, so do not keep asking"
);
}
#[test]
fn the_test_commit_budget_is_respected() {
let layers: Vec<(LayerId, Layer)> = (0..3)
.map(|i| (LayerId(i + 1), layer_at(0, 0, 1920, 1080, i)))
.collect();
let mut allocator = Allocator::new();
allocator.set_max_test_commits(2);
let mut committer = Committer {
reject_first: usize::MAX,
..Committer::default()
};
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut committer)
.expect("allocate");
assert!(
committer.calls <= 2,
"made {} test commits",
committer.calls
);
assert_eq!(result.diagnostics.test_commits_issued, committer.calls);
}
#[test]
fn fb_only_frame_skips_the_test_commit() {
let mut layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 1");
assert_eq!(first.assignment.len(), 2);
assert!(first.diagnostics.test_commits_issued > 0);
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&layers)
.into_iter()
.find(|e| e.id == *layer_id)
.expect("layer");
allocator.record_committed(*plane_id, entry);
}
for (_, layer) in &mut layers {
layer.set_property(PropTag::FbId, 99);
}
let calls_before = committer.calls;
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 2");
assert!(
second.diagnostics.fb_delta_fast_path,
"the fast path must engage"
);
assert_eq!(second.diagnostics.test_commits_issued, 0);
assert_eq!(committer.calls, calls_before, "no test commit was issued");
assert_eq!(second.assignment, first.assignment, "same placement reused");
}
#[test]
fn a_placement_change_defeats_the_fast_path() {
let mut layers = vec![(LayerId(1), layer_at(0, 0, 1920, 1080, 0))];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 1");
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&layers)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
layers[0].1.set_property(PropTag::CrtcX, 64);
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 2");
assert!(
!second.diagnostics.fb_delta_fast_path,
"a geometry change must force re-validation"
);
assert!(second.diagnostics.test_commits_issued > 0);
}
#[test]
fn a_vanished_layer_defeats_the_fast_path() {
let layers = vec![
(LayerId(1), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 1");
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&layers)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
let fewer = vec![(LayerId(1), layer_at(0, 0, 1920, 1080, 0))];
let second = allocator
.allocate(&refs(&fewer), ®istry, 0, &mut committer)
.expect("frame 2");
assert!(!second.diagnostics.fb_delta_fast_path);
}
#[test]
fn inserting_a_layer_does_not_reshuffle_the_existing_ones() {
let overlay_a = layer_at(0, 0, 640, 480, 1);
let overlay_b = layer_at(700, 0, 640, 480, 2);
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first_layers = vec![
(LayerId(1), overlay_a.clone()),
(LayerId(2), overlay_b.clone()),
];
let first = allocator
.allocate(&refs(&first_layers), ®istry, 0, &mut committer)
.expect("frame 1");
assert_eq!(first.assignment.len(), 2);
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&first_layers)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
let plane_of_1 = first
.assignment
.entries()
.iter()
.find(|(_, id)| *id == LayerId(1))
.map(|(p, _)| *p)
.expect("layer 1 placed");
let plane_of_2 = first
.assignment
.entries()
.iter()
.find(|(_, id)| *id == LayerId(2))
.map(|(p, _)| *p)
.expect("layer 2 placed");
let second_layers = vec![
(LayerId(3), layer_at(0, 0, 1920, 1080, 0)),
(LayerId(1), overlay_a),
(LayerId(2), overlay_b),
];
let second = allocator
.allocate(&refs(&second_layers), ®istry, 0, &mut committer)
.expect("frame 2");
assert_eq!(
second.assignment.get(plane_of_1),
Some(LayerId(1)),
"layer 1 must keep plane {plane_of_1}: reprogramming it is the flicker"
);
assert_eq!(
second.assignment.get(plane_of_2),
Some(LayerId(2)),
"layer 2 must keep plane {plane_of_2}"
);
}
#[test]
fn the_stability_bonus_never_reduces_placements() {
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first_layers = vec![(LayerId(1), layer_at(0, 0, 640, 480, 1))];
let first = allocator
.allocate(&refs(&first_layers), ®istry, 0, &mut committer)
.expect("frame 1");
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&first_layers)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
let second_layers = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 1)),
(LayerId(2), layer_at(700, 0, 640, 480, 2)),
(LayerId(3), layer_at(0, 600, 640, 400, 0)),
];
let second = allocator
.allocate(&refs(&second_layers), ®istry, 0, &mut committer)
.expect("frame 2");
assert_eq!(
second.assignment.len(),
3,
"the stability bonus orders preferences; it must not cost a placement"
);
}
#[test]
fn invalidate_allocation_forces_a_full_search() {
let layers = vec![(LayerId(1), layer_at(0, 0, 1920, 1080, 0))];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 1");
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&layers)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
allocator.invalidate_allocation();
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("frame 2");
assert!(
!second.diagnostics.fb_delta_fast_path,
"invalidation must defeat the fast path too"
);
assert!(second.diagnostics.test_commits_issued > 0);
}
#[test]
fn a_new_layer_in_steady_state_is_not_composited_forever() {
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let registry = registry();
let base = vec![(LayerId(1), layer_at(0, 0, 640, 480, 1))];
let first = allocator
.allocate(&refs(&base), ®istry, 0, &mut committer)
.expect("frame 1");
for (plane_id, layer_id) in first.assignment.entries() {
let entry = refs(&base).into_iter().find(|e| e.id == *layer_id).unwrap();
allocator.record_committed(*plane_id, entry);
}
assert_eq!(first.assignment.len(), 1);
let grown = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 1)),
(LayerId(2), layer_at(700, 0, 640, 480, 2)),
];
let second = allocator
.allocate(&refs(&grown), ®istry, 0, &mut committer)
.expect("frame 2");
assert!(
!second.diagnostics.fb_delta_fast_path,
"a new layer must defeat the fast path, or it can never be placed"
);
assert_eq!(second.assignment.len(), 2, "the new layer must get a plane");
assert!(second.composited.is_empty());
for (plane_id, layer_id) in second.assignment.entries() {
let entry = refs(&grown)
.into_iter()
.find(|e| e.id == *layer_id)
.unwrap();
allocator.record_committed(*plane_id, entry);
}
let third = allocator
.allocate(&refs(&grown), ®istry, 0, &mut committer)
.expect("frame 3");
assert!(
third.diagnostics.fb_delta_fast_path,
"with the set stable again, the fast path resumes"
);
assert_eq!(third.assignment.len(), 2);
}
fn fixed_order_registry() -> PlaneRegistry {
PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, None),
plane(32, PlaneType::Overlay, None),
plane(33, PlaneType::Overlay, None),
plane(34, PlaneType::Overlay, None),
])
}
#[test]
fn layers_are_assigned_bottom_up_when_planes_cannot_be_reordered() {
let registry = fixed_order_registry();
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let top = layer_at(400, 200, 256, 256, 2);
let bottom = layer_at(0, 0, 1024, 768, 0);
let middle = layer_at(64, 64, 256, 256, 1);
let layers = [
LayerRef {
id: LayerId(10),
layer: &top,
},
LayerRef {
id: LayerId(11),
layer: &bottom,
},
LayerRef {
id: LayerId(12),
layer: &middle,
},
];
let allocation = allocator
.allocate(&layers, ®istry, 0, &mut committer)
.expect("a valid assignment exists");
let plane_of = |id: LayerId| {
allocation
.assignment
.get_plane_of(id)
.unwrap_or_else(|| panic!("{id:?} was not placed"))
};
let (bottom_plane, middle_plane, top_plane) = (
plane_of(LayerId(11)),
plane_of(LayerId(12)),
plane_of(LayerId(10)),
);
assert!(
bottom_plane < middle_plane && middle_plane < top_plane,
"stacking is plane order here, so zpos 0/1/2 must land on ascending \
planes; got {bottom_plane}, {middle_plane}, {top_plane}"
);
}
fn plane_with(id: u32, plane_type: PlaneType, modifiers: &[u64]) -> PlaneCapabilities {
let mut cap = plane(id, plane_type, None);
cap.has_format_modifiers = true;
cap.format_table = drmkit_fmt::FormatTable::from_pairs(
modifiers
.iter()
.map(|m| (fourcc::XRGB8888, drmkit_fmt::Modifier(*m))),
);
cap
}
const LINEAR: u64 = 0;
const AFBC: u64 = 0x0800_0000_0000_0001;
#[test]
fn a_linear_only_overlay_is_still_a_candidate_when_the_primary_is_compressed_only() {
let registry = PlaneRegistry::from_capabilities(vec![
plane_with(31, PlaneType::Primary, &[AFBC]),
plane_with(32, PlaneType::Overlay, &[LINEAR]),
]);
assert_eq!(
registry.candidate_modifiers(0, fourcc::XRGB8888),
vec![LINEAR, AFBC],
"intersecting against the primary alone would answer nothing here"
);
}
#[test]
fn a_modifier_several_planes_share_is_reported_once() {
let registry = PlaneRegistry::from_capabilities(vec![
plane_with(31, PlaneType::Primary, &[LINEAR, AFBC]),
plane_with(32, PlaneType::Overlay, &[LINEAR]),
plane_with(33, PlaneType::Overlay, &[LINEAR]),
]);
assert_eq!(
registry.candidate_modifiers(0, fourcc::XRGB8888),
vec![LINEAR, AFBC]
);
}
#[test]
fn the_cursor_planes_modifiers_are_not_offered() {
let registry = PlaneRegistry::from_capabilities(vec![
plane_with(31, PlaneType::Primary, &[LINEAR]),
plane_with(34, PlaneType::Cursor, &[AFBC]),
]);
assert_eq!(
registry.candidate_modifiers(0, fourcc::XRGB8888),
vec![LINEAR],
"the cursor path owns that plane; a scanout layer never lands there"
);
}
#[test]
fn a_format_no_plane_scans_out_has_no_candidates() {
let registry =
PlaneRegistry::from_capabilities(vec![plane_with(31, PlaneType::Primary, &[LINEAR])]);
assert!(registry.candidate_modifiers(0, fourcc::NV12).is_empty());
}
#[test]
fn a_plane_on_another_crtc_contributes_nothing() {
let mut other = plane_with(32, PlaneType::Overlay, &[AFBC]);
other.possible_crtcs = 0b10;
let registry = PlaneRegistry::from_capabilities(vec![
plane_with(31, PlaneType::Primary, &[LINEAR]),
other,
]);
assert_eq!(
registry.candidate_modifiers(0, fourcc::XRGB8888),
vec![LINEAR]
);
}
#[test]
fn a_layer_above_zero_can_take_a_lone_fixed_primary() {
let registry =
PlaneRegistry::from_capabilities(vec![plane(31, PlaneType::Primary, Some((0, 0)))]);
let layers = vec![(LayerId(1), layer_at(0, 0, 640, 480, 1))];
let mut allocator = Allocator::new();
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(result.assignment.get(31), Some(LayerId(1)));
assert!(result.composited.is_empty());
}
#[test]
fn a_zpos_past_the_overlays_range_still_reaches_it() {
let layers = vec![(LayerId(1), layer_at(0, 0, 640, 480, 9))];
let mut allocator = Allocator::new();
let result = allocator
.allocate(&refs(&layers), ®istry(), 0, &mut Committer::default())
.expect("allocate");
assert_eq!(
result.assignment.len(),
1,
"zpos 9 against overlays of [1, 4]"
);
}
#[test]
fn an_inverted_stack_is_never_chosen() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(40, PlaneType::Primary, Some((2, 2))),
plane(41, PlaneType::Overlay, Some((0, 255))),
]);
let layers = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 5)),
(LayerId(2), layer_at(0, 0, 640, 480, 3)),
];
let mut allocator = Allocator::new();
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let inverted = result.assignment.get(40) == Some(LayerId(1))
&& result.assignment.get(41) == Some(LayerId(2));
assert!(
!inverted,
"{:?} stacks the 3 above the 5",
result.assignment.entries()
);
}
#[test]
fn a_warm_start_that_would_invert_the_stack_is_not_reused() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(40, PlaneType::Primary, Some((2, 2))),
plane(41, PlaneType::Overlay, Some((0, 255))),
]);
let mut layers = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 5)),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(first.assignment.get(40), Some(LayerId(1)));
assert_eq!(first.assignment.get(41), Some(LayerId(2)));
layers[1].1.set_property(PropTag::Zpos, 3);
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let inverted = second.assignment.get(40) == Some(LayerId(1))
&& second.assignment.get(41) == Some(LayerId(2));
assert!(
!inverted,
"the cached assignment now inverts the stack and was kept"
);
}
fn stacked_layers(count: u64) -> Vec<(LayerId, Layer)> {
(0..count)
.map(|zpos| (LayerId(zpos + 1), layer_at(0, 0, 64, 64, zpos)))
.collect()
}
fn planes_in_zpos_order(
allocation: &drmkit_planes::Allocation,
layers: &[(LayerId, Layer)],
) -> Vec<Option<u32>> {
let mut by_zpos: Vec<&(LayerId, Layer)> = layers.iter().collect();
by_zpos.sort_by_key(|(_, layer)| layer.property(PropTag::Zpos));
by_zpos
.iter()
.map(|(id, _)| allocation.assignment.get_plane_of(*id))
.collect()
}
#[test]
fn overflow_composites_one_run_on_a_canvas_between_its_neighbors() {
let registry = fixed_order_registry();
let layers = stacked_layers(6);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(
planes_in_zpos_order(&allocation, &layers),
[None, None, None, Some(32), Some(33), Some(34)],
"three placed on planes, the run below them on the canvas"
);
assert_eq!(allocator.canvas_plane(), Some(31));
let again = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(again.assignment.entries(), allocation.assignment.entries());
assert_eq!(allocator.canvas_plane(), Some(31));
}
#[test]
fn the_composited_run_takes_the_low_priority_layers() {
let registry = fixed_order_registry();
let mut layers = stacked_layers(6);
for (id, layer) in &mut layers {
layer.set_app_priority(if (3..=5).contains(&id.0) { 10 } else { 200 });
}
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(
planes_in_zpos_order(&allocation, &layers),
[Some(31), Some(32), None, None, None, Some(34)]
);
assert_eq!(allocator.canvas_plane(), Some(33));
}
#[test]
fn a_restack_is_not_served_from_the_warm_start() {
let registry = fixed_order_registry();
let mut layers = stacked_layers(3);
let mut allocator = Allocator::new();
allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
for (index, (_, layer)) in layers.iter_mut().enumerate() {
layer.set_property(PropTag::Zpos, 2 - index as u64);
}
let restacked = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let planes = planes_in_zpos_order(&restacked, &layers);
assert!(
planes.windows(2).all(|pair| pair[0] < pair[1]),
"zpos order must be plane order after the restack; got {planes:?}"
);
}
#[test]
fn out_of_tests_the_whole_stack_goes_to_the_canvas() {
let registry = fixed_order_registry();
let layers = stacked_layers(3);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
allocator.set_max_test_commits(2);
let mut committer = Committer {
reject_first: usize::MAX,
..Committer::default()
};
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(committer.seen, [vec![32, 33, 34], vec![33, 34]]);
assert!(allocation.assignment.is_empty());
assert_eq!(allocation.composited.len(), 3);
assert!(allocation.diagnostics.budget_exhausted);
assert_eq!(allocator.canvas_plane(), Some(34));
}
#[test]
fn plane_order_leaves_the_cursor_and_pinned_planes_alone() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, None),
plane(32, PlaneType::Overlay, None),
plane(33, PlaneType::Overlay, None),
plane(34, PlaneType::Cursor, None),
]);
let mut layers = stacked_layers(4);
layers[3].1.set_pinned(true).set_assigned_plane(Some(33));
let mut allocator = Allocator::new();
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let used: Vec<u32> = allocation
.assignment
.entries()
.iter()
.map(|(plane, _)| *plane)
.collect();
assert_eq!(used.len(), 2, "two free planes for three layers: {used:?}");
assert!(!used.contains(&33) && !used.contains(&34), "{used:?}");
assert_eq!(allocation.composited.len(), 1);
assert_eq!(allocator.canvas_plane(), None);
}
#[test]
fn a_zpos_tie_composites_its_lowest_priority_layers() {
let registry = fixed_order_registry();
let mut layers: Vec<(LayerId, Layer)> = (1..=6)
.map(|id| (LayerId(id), layer_at(0, 0, 64, 64, 0)))
.collect();
for (id, layer) in &mut layers {
layer.set_app_priority(u8::try_from(250 - id.0 * 10).expect("small"));
}
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let mut composited: Vec<u64> = allocation.composited.iter().map(|id| id.0).collect();
composited.sort_unstable();
assert_eq!(composited, [4, 5, 6], "the three lowest priorities");
}
fn composited_scene(low: &[u64]) -> Vec<(LayerId, Layer)> {
let mut layers = stacked_layers(6);
for (id, layer) in &mut layers {
layer.set_app_priority(if low.contains(&id.0) { 10 } else { 200 });
}
layers
}
fn composited_ids(allocation: &drmkit_planes::Allocation) -> Vec<u64> {
let mut ids: Vec<u64> = allocation.composited.iter().map(|id| id.0).collect();
ids.sort_unstable();
ids
}
#[test]
fn a_composited_layer_is_not_new_while_no_plane_is_free() {
let registry = fixed_order_registry();
let mut layers = composited_scene(&[1, 2, 3]);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(composited_ids(&first), [1, 2, 3]);
for (id, layer) in &mut layers {
layer.set_app_priority(if id.0 >= 4 { 10 } else { 200 });
}
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(second.diagnostics.test_commits_issued, 1);
assert_eq!(second.assignment.entries(), first.assignment.entries());
assert_eq!(composited_ids(&second), [1, 2, 3]);
assert_eq!(allocator.canvas_plane(), Some(31));
}
#[test]
fn a_lone_composited_layer_takes_the_canvas_plane_once_it_fits() {
let registry = fixed_order_registry();
let mut layers = composited_scene(&[1, 2, 3]);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
layers.retain(|(id, _)| id.0 != 2 && id.0 != 3);
let shrunk = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert!(shrunk.composited.is_empty(), "{:?}", shrunk.composited);
assert_eq!(shrunk.assignment.len(), 4);
assert_eq!(allocator.canvas_plane(), None);
}
#[test]
fn a_scene_back_under_the_plane_count_retires_the_canvas() {
let registry = fixed_order_registry();
let mut layers = composited_scene(&[1, 2, 3]);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
layers.retain(|(id, _)| id.0 <= 4);
let shrunk = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert!(shrunk.composited.is_empty(), "{:?}", shrunk.composited);
assert_eq!(
planes_in_zpos_order(&shrunk, &layers),
[Some(31), Some(32), Some(33), Some(34)]
);
assert_eq!(allocator.canvas_plane(), None);
}
#[test]
fn a_plane_limit_verdict_holds_while_its_planes_stay_free() {
let registry = fixed_order_registry();
let layers = stacked_layers(3);
let mut allocator = Allocator::new();
allocator.set_canvas(&[31, 32, 33, 34], Some(&Layer::new()));
let mut committer = Committer {
max_planes: Some(1),
..Committer::default()
};
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(first.assignment.len(), 1);
assert_eq!(first.composited.len(), 2);
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(
second.diagnostics.test_commits_issued, 1,
"one warm-start test"
);
assert_eq!(second.assignment.entries(), first.assignment.entries());
}
fn zpos_registry() -> PlaneRegistry {
PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, Some((0, 0))),
plane(32, PlaneType::Overlay, Some((1, 8))),
plane(33, PlaneType::Overlay, Some((1, 8))),
plane(34, PlaneType::Overlay, Some((1, 8))),
])
}
#[test]
fn a_held_canvas_plane_is_tested_with_the_layers_and_kept_with_them() {
let registry = zpos_registry();
let layers = stacked_layers(3);
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34], Some(&Layer::new()));
let mut committer = Committer {
max_planes: Some(2),
count_extra: true,
..Committer::default()
};
allocator.set_reserved_planes(&[34]);
allocator.hold_canvas_plane(Some(34));
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(first.assignment.len(), 1, "{:?}", first.assignment);
assert_eq!(first.composited.len(), 2);
assert_eq!(allocator.canvas_plane(), Some(34));
assert!(
committer.extras_seen.iter().all(|extra| *extra == Some(34)),
"every test arms the canvas: {:?}",
committer.extras_seen
);
allocator.set_reserved_planes(&[]);
allocator.hold_canvas_plane(None);
committer.extras_seen.clear();
let second = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(
second.diagnostics.test_commits_issued, 1,
"one warm-start test"
);
assert_eq!(committer.extras_seen, [Some(34)], "with the canvas armed");
assert_eq!(second.assignment.entries(), first.assignment.entries());
assert_eq!(allocator.canvas_plane(), Some(34));
}
#[test]
fn a_held_canvas_plane_goes_back_to_the_layers_once_they_fit() {
let registry = zpos_registry();
let mut layers = stacked_layers(5);
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34], Some(&Layer::new()));
allocator.set_reserved_planes(&[34]);
allocator.hold_canvas_plane(Some(34));
let first = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(first.composited.len(), 2, "{:?}", first.composited);
let gone = first.composited[0];
layers.retain(|(id, _)| *id != gone);
allocator.set_reserved_planes(&[]);
allocator.hold_canvas_plane(None);
let shrunk = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert!(shrunk.composited.is_empty(), "{:?}", shrunk.composited);
assert_eq!(shrunk.assignment.len(), 4);
assert_eq!(allocator.canvas_plane(), None);
}
fn sde_plane(id: u32, formats: &[u32], parent: Option<u32>) -> PlaneCapabilities {
PlaneCapabilities {
formats: formats.to_vec(),
multirect_parent: parent,
..plane(id, PlaneType::Overlay, Some((0, 10)))
}
}
fn argb_layer(zpos: u64) -> Layer {
let mut layer = layer_at(0, 0, 640, 480, zpos);
layer.set_property(PropTag::PixelFormat, u64::from(fourcc::ARGB8888));
layer
}
#[test]
fn a_virtual_plane_is_never_placed_without_its_parent() {
let registry = PlaneRegistry::from_capabilities(vec![
sde_plane(115, &[fourcc::XRGB8888], None),
sde_plane(118, &[fourcc::ARGB8888], Some(115)),
]);
let layers = vec![(LayerId(1), argb_layer(1))];
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert!(result.assignment.is_empty(), "{:?}", result.assignment);
assert_eq!(result.composited, vec![LayerId(1)]);
}
#[test]
fn a_virtual_plane_is_placed_beside_its_parent() {
let registry = PlaneRegistry::from_capabilities(vec![
sde_plane(114, &[fourcc::XRGB8888, fourcc::ARGB8888], Some(115)),
sde_plane(115, &[fourcc::XRGB8888, fourcc::ARGB8888], None),
]);
let layers = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 1)),
(LayerId(2), layer_at(0, 0, 640, 480, 2)),
];
let mut allocator = Allocator::new();
let mut committer = Committer {
multirect: vec![(114, 115)],
..Committer::default()
};
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(result.assignment.len(), 2, "{:?}", result.assignment);
assert!(result.composited.is_empty());
assert_eq!(committer.orphans_tested, 0, "{:?}", committer.seen);
}
#[test]
fn dropping_a_parent_drops_its_virtual_plane() {
let registry = PlaneRegistry::from_capabilities(vec![
sde_plane(115, &[fourcc::XRGB8888], None),
sde_plane(118, &[fourcc::XRGB8888], Some(115)),
]);
let mut video = layer_at(0, 0, 640, 480, 2);
video.set_content_type(ContentType::Video);
let layers = vec![
(LayerId(1), video),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let mut committer = Committer {
max_planes: Some(1),
multirect: vec![(118, 115)],
..Committer::default()
};
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(committer.orphans_tested, 0, "{:?}", committer.seen);
assert!(result.assignment.is_empty(), "{:?}", result.assignment);
}
#[test]
fn plane_order_leaves_virtual_planes_out() {
let registry = PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, None),
PlaneCapabilities {
multirect_parent: Some(33),
..plane(32, PlaneType::Overlay, None)
},
plane(33, PlaneType::Overlay, None),
]);
let layers = vec![
(LayerId(1), layer_at(0, 0, 640, 480, 0)),
(LayerId(2), layer_at(0, 0, 640, 480, 1)),
];
let mut allocator = Allocator::new();
let mut committer = Committer {
multirect: vec![(32, 33)],
..Committer::default()
};
let result = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(committer.orphans_tested, 0, "{:?}", committer.seen);
assert_eq!(result.assignment.get(31), Some(LayerId(1)));
assert_eq!(result.assignment.get(33), Some(LayerId(2)));
}
#[test]
fn the_canvas_stacks_under_the_placed_layers_that_cover_its_run() {
let registry = zpos_registry();
let mut layers = stacked_layers(8);
for (id, layer) in &mut layers {
layer.set_app_priority(if (3..=6).contains(&id.0) { 10 } else { 200 });
}
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34], Some(&Layer::new()));
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let placed: Vec<bool> = planes_in_zpos_order(&allocation, &layers)
.iter()
.map(Option::is_some)
.collect();
assert_eq!(
placed,
[true, true, false, false, false, false, true, true],
"{:?}",
allocation.assignment
);
assert_eq!(
allocator.canvas_zpos(),
Some(6),
"the canvas slot is under layer 6, the lowest placed one above the run"
);
}
#[test]
fn split_low_priority_layers_composite_one_run() {
let registry = zpos_registry();
let mut layers = stacked_layers(8);
for (id, layer) in &mut layers {
let low = matches!(id.0, 2 | 3 | 6 | 7);
layer.set_app_priority(if low { 10 } else { 200 });
}
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34], Some(&Layer::new()));
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let placed: Vec<bool> = planes_in_zpos_order(&allocation, &layers)
.iter()
.map(Option::is_some)
.collect();
let first = placed
.iter()
.position(|p| !p)
.expect("something composited");
let last = placed
.iter()
.rposition(|p| !p)
.expect("something composited");
assert!(
placed[first..=last].iter().all(|p| !p),
"the composited layers must be one zpos run: {placed:?}"
);
assert_eq!(
allocator.canvas_zpos(),
None,
"the run is the top of the stack, so the canvas stacks on top: {placed:?}"
);
}
#[test]
fn a_restack_that_splits_the_run_is_searched_again() {
let registry = zpos_registry();
let mut layers = stacked_layers(8);
for (id, layer) in &mut layers {
layer.set_app_priority(if (3..=6).contains(&id.0) { 10 } else { 200 });
}
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34], Some(&Layer::new()));
allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("the first frame");
layers[3].1.set_property(PropTag::Zpos, 10);
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("the restacked frame");
let placed: Vec<bool> = planes_in_zpos_order(&allocation, &layers)
.iter()
.map(Option::is_some)
.collect();
let first = placed
.iter()
.position(|p| !p)
.expect("something composited");
let last = placed
.iter()
.rposition(|p| !p)
.expect("something composited");
assert!(
placed[first..=last].iter().all(|p| !p),
"the composited layers must be one zpos run again: {placed:?}"
);
}
fn stage_limited_registry() -> PlaneRegistry {
let mut registry = PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary, Some((0, 10))),
plane(32, PlaneType::Overlay, Some((0, 10))),
plane(33, PlaneType::Overlay, Some((0, 10))),
plane(34, PlaneType::Overlay, Some((0, 10))),
plane(35, PlaneType::Overlay, Some((0, 10))),
]);
registry.set_blend_stage_limit(0, Some(3));
registry
}
#[test]
fn no_test_arms_more_planes_than_the_crtc_can_stack() {
let registry = stage_limited_registry();
let layers = stacked_layers(5);
let mut allocator = Allocator::new();
let mut committer = Committer::default();
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(
allocation.assignment.len(),
2,
"{:?}",
allocation.assignment
);
assert_eq!(allocation.composited.len(), 3);
assert!(!committer.seen.is_empty());
assert!(
committer.seen.iter().all(|planes| planes.len() <= 3),
"a test armed more than the CRTC stacks: {:?}",
committer.seen
);
}
#[test]
fn the_blend_limit_keeps_the_layers_most_worth_a_plane() {
let registry = stage_limited_registry();
let mut layers = stacked_layers(5);
for (id, layer) in &mut layers {
if id.0 == 1 || id.0 == 5 {
layer.set_app_priority(200);
}
}
let allocation = Allocator::new()
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
let mut placed: Vec<u64> = allocation
.assignment
.entries()
.iter()
.map(|(_, id)| id.0)
.collect();
placed.sort_unstable();
assert_eq!(placed, vec![1, 5], "{:?}", allocation.assignment);
}
#[test]
fn the_canvas_counts_against_the_stage_limit() {
let registry = stage_limited_registry();
let layers = stacked_layers(5);
let mut allocator = Allocator::new();
allocator.set_canvas(&[32, 33, 34, 35], Some(&Layer::new()));
allocator.set_reserved_planes(&[35]);
allocator.hold_canvas_plane(Some(35));
let mut committer = Committer::default();
let allocation = allocator
.allocate(&refs(&layers), ®istry, 0, &mut committer)
.expect("allocate");
assert_eq!(
allocation.assignment.len(),
2,
"{:?}",
allocation.assignment
);
assert!(
committer
.seen
.iter()
.zip(&committer.extras_seen)
.all(|(planes, extra)| planes.len() + usize::from(extra.is_some()) <= 3),
"a test armed more than the CRTC stacks: {:?} {:?}",
committer.seen,
committer.extras_seen
);
}
#[test]
fn without_a_limit_every_plane_is_offered() {
let mut registry = stage_limited_registry();
registry.set_blend_stage_limit(0, None);
let layers = stacked_layers(5);
let allocation = Allocator::new()
.allocate(&refs(&layers), ®istry, 0, &mut Committer::default())
.expect("allocate");
assert_eq!(allocation.assignment.len(), 5);
}