use std::cell::{Cell, RefCell};
use std::rc::Rc;
use drmkit_fmt::fourcc;
use drmkit_planes::{
LayerRef, PlaneCapabilities, PlaneRegistry, PlaneType, TestCommitter, TestFailure,
};
use drmkit_scene::{
AcquiredBuffer, ColorEncoding, ColorRange, CommitKind, DamageRect, DisplayParams, KernelResult,
LayerBufferSource, LayerScene, Rect, SceneError, SourceError, SourceFormat, Ycbcr,
};
fn registry() -> PlaneRegistry {
PlaneRegistry::from_capabilities(vec![
PlaneCapabilities {
id: 31,
possible_crtcs: 0b1,
plane_type: PlaneType::Primary,
formats: vec![fourcc::XRGB8888],
supports_scaling: true,
..PlaneCapabilities::default()
},
PlaneCapabilities {
id: 32,
possible_crtcs: 0b1,
plane_type: PlaneType::Overlay,
formats: vec![fourcc::XRGB8888],
supports_scaling: true,
..PlaneCapabilities::default()
},
])
}
#[derive(Debug, Default)]
struct SourceLog {
acquired: usize,
released: Vec<u32>,
}
struct TestSource {
log: Rc<RefCell<SourceLog>>,
next_fb: u32,
starved: bool,
fails: bool,
toggle: Rc<Cell<bool>>,
damage: Vec<DamageRect>,
}
impl TestSource {
fn new(log: &Rc<RefCell<SourceLog>>) -> Self {
Self {
log: Rc::clone(log),
next_fb: 100,
starved: false,
fails: false,
toggle: Rc::new(Cell::new(false)),
damage: Vec::new(),
}
}
fn starve_switch(&self) -> Rc<Cell<bool>> {
Rc::clone(&self.toggle)
}
}
impl LayerBufferSource for TestSource {
fn acquire(&mut self) -> Result<AcquiredBuffer, SourceError> {
if self.fails {
return Err(SourceError::Failed(rustix::io::Errno::IO));
}
if self.starved || self.toggle.get() {
return Err(SourceError::WouldBlock);
}
self.log.borrow_mut().acquired += 1;
let fb_id = self.next_fb;
self.next_fb += 1;
Ok(AcquiredBuffer {
fb_id,
damage: self.damage.clone(),
..AcquiredBuffer::default()
})
}
fn release(&mut self, acquired: AcquiredBuffer) {
self.log.borrow_mut().released.push(acquired.fb_id);
}
fn format(&self) -> SourceFormat {
SourceFormat {
fourcc: fourcc::XRGB8888,
modifier: 0,
width: 1920,
height: 1080,
}
}
}
#[derive(Debug, Default)]
struct Accepting {
calls: usize,
}
impl TestCommitter for Accepting {
fn test_assignment(&mut self, _assignment: &[(u32, LayerRef<'_>)]) -> Result<(), TestFailure> {
self.calls += 1;
Ok(())
}
}
fn full_screen() -> DisplayParams {
DisplayParams {
dst_rect: Rect {
x: 0,
y: 0,
w: 1920,
h: 1080,
},
..DisplayParams::default()
}
}
fn real() -> CommitKind {
CommitKind::Real { arms_flip: true }
}
#[test]
fn a_layer_is_acquired_lowered_placed_and_released() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert_eq!(build.held(), 1, "the source contributed a buffer");
let report = scene.finalize_frame(build, KernelResult::Ok);
assert_eq!(report.layers_total, 1);
assert_eq!(report.layers_assigned, 1, "one plane, one layer");
assert_eq!(report.layers_skipped_no_frame, 0);
assert!(report.accounting_balances());
assert_eq!(log.borrow().acquired, 1);
assert!(
log.borrow().released.is_empty(),
"invariant 1: the buffer is on screen, not back yet"
);
assert!(scene.has_pending_flip());
scene.flip_landed();
for _ in 0..2 {
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
}
assert_eq!(
log.borrow().released,
vec![100],
"invariant 1: released two commits deep, in order"
);
scene.drain();
}
#[test]
fn a_sources_damage_reaches_the_plan_clipped_to_its_buffer() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let registry = registry();
let mut committer = Accepting::default();
let mut source = TestSource::new(&log);
source.damage = vec![
DamageRect {
x: 1900,
y: -10,
w: 40,
h: 30,
},
DamageRect {
x: 2000,
y: 0,
w: 8,
h: 8,
},
];
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(source));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert_eq!(
build.plan()[0].damage,
[DamageRect {
x: 1900,
y: 0,
w: 20,
h: 20
}],
"clipped to the buffer, and the rectangle wholly off it dropped"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
let mut source = TestSource::new(&log);
source.damage = (0..17)
.map(|i| DamageRect {
x: i * 10,
y: 0,
w: 4,
h: 4,
})
.collect();
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(source));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert!(
build.plan()[0].damage.is_empty(),
"seventeen rectangles is a whole-frame repaint"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}
#[test]
fn a_starved_source_is_skipped_not_failed() {
let live_log = Rc::new(RefCell::new(SourceLog::default()));
let starved_log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let live = scene.add_layer(Box::new(TestSource::new(&live_log)));
let mut starved_source = TestSource::new(&starved_log);
starved_source.starved = true;
scene.add_layer(Box::new(starved_source));
scene
.layer_mut(live)
.expect("layer")
.set_display(full_screen());
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("a starved source must not abort the build");
let report = scene.finalize_frame(build, KernelResult::Ok);
assert_eq!(report.layers_total, 2);
assert_eq!(report.layers_skipped_no_frame, 1);
assert_eq!(report.layers_assigned, 1);
assert!(report.accounting_balances());
assert_eq!(starved_log.borrow().acquired, 0);
scene.flip_landed();
scene.drain();
}
#[test]
fn a_failing_source_aborts_the_build_and_returns_what_was_taken() {
let good_log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
scene.add_layer(Box::new(TestSource::new(&good_log)));
let mut failing = TestSource::new(&Rc::new(RefCell::new(SourceLog::default())));
failing.fails = true;
scene.add_layer(Box::new(failing));
let mut committer = Accepting::default();
let result = scene.build_frame(®istry(), 0, real(), &mut committer);
assert!(
matches!(result, Err(SceneError::Source(SourceError::Failed(_)))),
"a real source failure must surface as such, not as a skip: {result:?}"
);
assert_eq!(
good_log.borrow().released,
vec![100],
"the buffer taken before the failure must not be stranded"
);
}
#[test]
fn a_rejected_commit_returns_buffers_and_keeps_the_scene_running() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::Rejected);
assert_eq!(
log.borrow().released,
vec![100],
"the rejected frame's buffer comes straight back"
);
assert!(!scene.is_suspended(), "a rejection is recoverable");
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("the scene kept running");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}
#[test]
fn lost_master_suspends_until_resumed() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::NotMaster);
assert!(scene.is_suspended());
assert!(
matches!(
scene.build_frame(®istry, 0, real(), &mut committer),
Err(SceneError::Suspended)
),
"a suspended scene short-circuits with Suspended, not some other error"
);
scene.resume();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("resumed");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}
#[test]
fn a_stale_handle_does_not_address_the_slots_new_occupant() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let first = scene.add_layer(Box::new(TestSource::new(&log)));
assert!(scene.layer(first).is_some());
assert!(scene.remove_layer(first));
assert!(scene.layer(first).is_none(), "removed");
let second = scene.add_layer(Box::new(TestSource::new(&log)));
assert!(scene.layer(second).is_some());
assert!(
scene.layer(first).is_none(),
"the stale handle must not resolve to the new occupant"
);
assert_ne!(
first.layer_id(),
second.layer_id(),
"and their allocator identities must differ, or warm-start state would \
carry across two unrelated layers"
);
assert!(!scene.remove_layer(first), "removing twice is a no-op");
}
#[test]
fn a_removed_layers_source_survives_until_its_buffers_return() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert!(log.borrow().released.is_empty());
assert!(scene.remove_layer(handle));
assert!(scene.is_empty());
scene.drain();
assert_eq!(
log.borrow().released,
vec![100],
"the buffer must reach the source that produced it, not vanish"
);
}
#[test]
fn an_empty_scene_builds_an_empty_frame() {
let mut scene = LayerScene::new(1);
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("build");
let report = scene.finalize_frame(build, KernelResult::Ok);
assert_eq!(report.layers_total, 0);
assert!(report.accounting_balances());
scene.flip_landed();
}
#[test]
fn changing_a_hint_forces_reallocation() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene
.layer_mut(handle)
.expect("layer")
.set_content_type(drmkit_planes::ContentType::Video);
assert!(scene.layer(handle).expect("layer").hints_dirty());
let calls_before = committer.calls;
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert!(
committer.calls > calls_before,
"a changed hint must force a re-search, not reuse the cached assignment"
);
assert!(
!scene.layer(handle).expect("layer").hints_dirty(),
"and the flag clears once the allocation has seen it"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}
#[test]
#[should_panic(expected = "page-flip event still armed")]
fn dropping_a_scene_with_an_armed_flip_is_caught() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("build");
scene.finalize_frame(build, KernelResult::Ok);
assert!(scene.has_pending_flip());
drop(scene);
}
#[test]
fn a_scene_without_an_armed_flip_drops_cleanly() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let mut committer = Accepting::default();
let build = scene
.build_frame(
®istry(),
0,
CommitKind::Real { arms_flip: false },
&mut committer,
)
.expect("build");
scene.finalize_frame(build, KernelResult::Ok);
assert!(!scene.has_pending_flip());
scene.drain();
}
#[test]
fn a_steady_frame_issues_no_test_commit() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let after_first = committer.calls;
assert!(
after_first > 0,
"the first frame has no baseline and must search"
);
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("second frame");
let took_fast_path = build.report().fb_delta_fast_path;
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert!(
took_fast_path,
"an unchanged layer set must take the FB-only fast path"
);
assert_eq!(
committer.calls,
after_first,
"a steady frame must not issue a test commit; it issued {}",
committer.calls - after_first
);
}
#[test]
fn removing_a_layer_does_not_force_a_search() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let keep = scene.add_layer(Box::new(TestSource::new(&log)));
let drop_me = scene.add_layer(Box::new(TestSource::new(&log)));
for handle in [keep, drop_me] {
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
}
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let after_first = committer.calls;
scene.remove_layer(drop_me);
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("frame after removal");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert_eq!(
committer.calls, after_first,
"removing a layer must not re-test the survivors"
);
}
#[test]
fn a_steady_frame_disables_nothing_and_diffs_everything() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame");
assert!(
build.plan().iter().all(|entry| entry.baseline.is_none()),
"the first frame has no baseline and must write every property"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("second frame");
let all_have_baselines = build.plan().iter().all(|entry| entry.baseline.is_some());
let disables = build.disables().to_vec();
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert!(
all_have_baselines,
"a plane the kernel already accepted must be diffed, not re-stated"
);
assert!(
disables.is_empty(),
"no plane changed hands, so nothing needs disabling; got {disables:?}"
);
}
#[test]
fn the_plane_a_removed_layer_held_is_disabled() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let keep = scene.add_layer(Box::new(TestSource::new(&log)));
let drop_me = scene.add_layer(Box::new(TestSource::new(&log)));
for handle in [keep, drop_me] {
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
}
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame");
let vacated = build
.plan()
.iter()
.find(|entry| entry.layer_id == drop_me.layer_id())
.map(|entry| entry.plane_id)
.expect("the layer being dropped must have been placed");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.remove_layer(drop_me);
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("frame after removal");
let disables = build.disables().to_vec();
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert!(
disables.contains(&vacated),
"plane {vacated} still holds the removed layer's framebuffer and was \
not disabled; disables were {disables:?}"
);
}
#[test]
fn until_a_commit_lands_every_unused_plane_is_turned_off() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("empty frame");
assert!(build.disables().is_empty(), "{:?}", build.disables());
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let unused = |build: &drmkit_scene::FrameBuild| -> Vec<u32> {
let mut planes: Vec<u32> = registry
.force_disable_candidates(0)
.map(|plane| plane.id)
.filter(|id| !build.plan().iter().any(|entry| entry.plane_id == *id))
.collect();
planes.sort_unstable();
planes
};
let sorted = |build: &drmkit_scene::FrameBuild| -> Vec<u32> {
let mut planes = build.disables().to_vec();
planes.sort_unstable();
planes
};
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame with a layer");
assert!(!unused(&build).is_empty(), "the case needs a spare plane");
assert_eq!(
sorted(&build),
unused(&build),
"the first commit clears the CRTC"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("settled frame");
assert!(build.disables().is_empty(), "{:?}", build.disables());
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.resume();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("frame after a resume");
assert_eq!(sorted(&build), unused(&build), "a resume clears it again");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
}
#[test]
fn a_starved_layer_holds_its_plane_and_the_report_still_balances() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let steady = scene.add_layer(Box::new(TestSource::new(&log)));
let hiccup_source = TestSource::new(&log);
let switch = hiccup_source.starve_switch();
let hiccup = scene.add_layer(Box::new(hiccup_source));
for handle in [steady, hiccup] {
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
}
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("first frame");
let plane_of_hiccup = build
.plan()
.iter()
.find(|entry| entry.layer_id == hiccup.layer_id())
.map(|entry| entry.plane_id)
.expect("both layers must be placed");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
switch.set(true);
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("starved frame");
let still_programmed = build
.plan()
.iter()
.any(|entry| entry.plane_id == plane_of_hiccup);
let disabled = build.disables().contains(&plane_of_hiccup);
let report = scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert!(
still_programmed,
"a starved layer must keep its plane and re-attach the frame it \
already had"
);
assert!(
!disabled,
"plane {plane_of_hiccup} was switched off; the layer would blink"
);
assert_eq!(
report.layers_skipped_no_frame, 1,
"the starved layer must be counted as skipped"
);
assert!(
report.accounting_balances(),
"report double-counts the starved layer: total {} != assigned {} + \
composited {} + unassigned {} + skipped {}",
report.layers_total,
report.layers_assigned,
report.layers_composited,
report.layers_unassigned,
report.layers_skipped_no_frame
);
scene.drain();
}
fn ranged_registry() -> PlaneRegistry {
let plane = |id, plane_type| PlaneCapabilities {
id,
possible_crtcs: 0b1,
plane_type,
formats: vec![fourcc::XRGB8888],
supports_scaling: true,
zpos_min: Some(0),
zpos_max: Some(10),
..PlaneCapabilities::default()
};
PlaneRegistry::from_capabilities(vec![
plane(31, PlaneType::Primary),
plane(32, PlaneType::Overlay),
plane(33, PlaneType::Overlay),
])
}
#[test]
fn zpos_is_written_densely_and_the_baseline_keeps_what_was_written() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let mut handles = Vec::new();
for (x, zpos) in [(0, 8), (960, 20)] {
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(DisplayParams {
dst_rect: Rect {
x,
y: 0,
w: 960,
h: 1080,
},
zpos: Some(zpos),
..DisplayParams::default()
});
handles.push(handle);
}
let registry = ranged_registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
let mut written: Vec<(u64, Option<u64>)> = build
.plan()
.iter()
.map(|entry| {
(
entry
.layer
.property(drmkit_planes::PropTag::Zpos)
.expect("asked"),
entry.zpos,
)
})
.collect();
written.sort_unstable();
assert_eq!(written, vec![(8, Some(0)), (20, Some(1))]);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
for entry in build.plan() {
assert_eq!(
entry
.baseline
.and_then(|b| b.get(drmkit_planes::PropTag::Zpos)),
entry.zpos,
"the baseline holds the zpos that went out, not the one asked for"
);
}
let report = scene.finalize_frame(build, KernelResult::Ok);
assert!(
report.fb_delta_fast_path,
"an unchanged frame still takes the fast path"
);
scene.flip_landed();
scene.drain();
}
fn commit_layers(
scene: &mut LayerScene,
log: &Rc<RefCell<SourceLog>>,
displays: &[DisplayParams],
) -> Vec<drmkit_scene::LayerHandle> {
let handles: Vec<_> = displays
.iter()
.map(|display| {
let handle = scene.add_layer(Box::new(TestSource::new(log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(*display);
handle
})
.collect();
let build = scene
.build_frame(®istry(), 0, real(), &mut Accepting::default())
.expect("frame");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
handles
}
#[test]
fn a_plane_left_by_replaced_layers_is_turned_off() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let mut top = full_screen();
top.zpos = Some(1);
let old = commit_layers(&mut scene, &log, &[full_screen(), top]);
for handle in old {
scene.remove_layer(handle);
}
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let build = scene
.build_frame(®istry(), 0, real(), &mut Accepting::default())
.expect("frame with the replacement");
let used: Vec<u32> = build.plan().iter().map(|entry| entry.plane_id).collect();
assert_eq!(used.len(), 1, "{used:?}");
let left = if used[0] == 31 { 32 } else { 31 };
assert_eq!(build.disables(), [left], "the old layer's plane goes off");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
}
#[test]
fn an_emptied_scene_keeps_its_planes() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let old = commit_layers(&mut scene, &log, &[full_screen()]);
for handle in old {
scene.remove_layer(handle);
}
let build = scene
.build_frame(®istry(), 0, real(), &mut Accepting::default())
.expect("empty frame");
assert!(build.disables().is_empty(), "{:?}", build.disables());
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
}
#[derive(Debug, Default)]
struct RefusingPlane {
plane: u32,
calls: usize,
}
impl TestCommitter for RefusingPlane {
fn test_assignment(&mut self, assignment: &[(u32, LayerRef<'_>)]) -> Result<(), TestFailure> {
self.calls += 1;
if assignment
.iter()
.any(|(plane_id, _)| *plane_id == self.plane)
{
Err(TestFailure::Rejected)
} else {
Ok(())
}
}
}
#[test]
fn a_pin_the_driver_refuses_is_caught_before_the_commit() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
let layer = scene.layer_mut(handle).expect("layer");
layer.set_display(full_screen());
layer.set_pinned_plane(Some(32));
let mut committer = RefusingPlane {
plane: 32,
..RefusingPlane::default()
};
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("the frame with the refused pin");
assert!(
build.plan().iter().all(|entry| entry.plane_id != 32),
"the refused plane is not programmed: {:?}",
build.plan()
);
let report = scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert_eq!(report.pin_requests_unhonored, 1, "{report:?}");
assert!(report.accounting_balances(), "{report:?}");
let tests_before = committer.calls;
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("the next frame");
let report = scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert_eq!(
report.pin_requests_unhonored, 1,
"still counted: {report:?}"
);
assert!(report.accounting_balances(), "{report:?}");
assert_eq!(
committer.calls - tests_before,
report.test_commits_issued,
"the refused pin is not tested again"
);
}
#[test]
fn an_accepted_pin_is_tested_once() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
let layer = scene.layer_mut(handle).expect("layer");
layer.set_display(full_screen());
layer.set_pinned_plane(Some(32));
let mut committer = Accepting::default();
for frame in 0..3 {
let build = scene
.build_frame(®istry(), 0, real(), &mut committer)
.expect("frame");
let placed: Vec<u32> = build.plan().iter().map(|entry| entry.plane_id).collect();
assert_eq!(placed, [32], "frame {frame}");
let report = scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
assert_eq!(report.pin_requests_unhonored, 0, "{report:?}");
}
assert_eq!(committer.calls, 1, "one test, for the pin");
}
#[test]
fn a_layers_ycbcr_rides_its_plan_entry() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let video = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(video)
.expect("layer")
.set_display(DisplayParams {
color_encoding: Some(ColorEncoding::Bt601),
color_range: Some(ColorRange::Full),
zpos: Some(0),
..full_screen()
});
let plain = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(plain)
.expect("layer")
.set_display(DisplayParams {
zpos: Some(1),
..full_screen()
});
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
let color_of = |handle: drmkit_scene::LayerHandle| {
build
.plan()
.iter()
.find(|entry| entry.layer_id == handle.layer_id())
.map(|entry| entry.color)
};
assert_eq!(
color_of(video),
Some(Some(Ycbcr {
encoding: ColorEncoding::Bt601,
range: ColorRange::Full,
})),
"the override reaches the plan"
);
assert_eq!(
color_of(plain),
Some(Some(Ycbcr {
encoding: ColorEncoding::Bt709,
range: ColorRange::Limited,
})),
"no override is BT.709 limited, upstream's default"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}
#[test]
fn sticky_properties_are_restated_until_the_kernel_is_known() {
let log = Rc::new(RefCell::new(SourceLog::default()));
let mut scene = LayerScene::new(1);
let handle = scene.add_layer(Box::new(TestSource::new(&log)));
scene
.layer_mut(handle)
.expect("layer")
.set_display(full_screen());
let registry = registry();
let mut committer = Accepting::default();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert!(build.restates_sticky(), "the first frame restates");
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("build");
assert!(
!build.restates_sticky(),
"once a frame has landed, what the kernel holds is known"
);
scene.finalize_frame(build, KernelResult::NotMaster);
assert!(scene.is_suspended());
scene.resume();
let build = scene
.build_frame(®istry, 0, real(), &mut committer)
.expect("resumed");
assert!(
build.restates_sticky(),
"after a resume another client may have changed them"
);
scene.finalize_frame(build, KernelResult::Ok);
scene.flip_landed();
scene.drain();
}