use crate::{LayerSpec, NarrowPolicy, SceneSet, SetError, SetLayerHandle, SlotState};
use crate::{Target, partition_for_policy};
fn engaged(wants_modeset: bool) -> SlotState {
SlotState {
is_hole: false,
wants_modeset,
}
}
const HOLE: SlotState = SlotState {
is_hole: true,
wants_modeset: false,
};
#[test]
fn an_empty_set_commits_nothing() {
for policy in [
NarrowPolicy::Combined,
NarrowPolicy::AutoOnModeset,
NarrowPolicy::PerCrtc,
] {
assert!(
partition_for_policy(&[], policy).is_empty(),
"{policy:?} invented a commit for a set with no scenes"
);
}
}
#[test]
fn a_set_of_only_holes_commits_nothing() {
let slots = [HOLE, HOLE, HOLE];
for policy in [
NarrowPolicy::Combined,
NarrowPolicy::AutoOnModeset,
NarrowPolicy::PerCrtc,
] {
assert!(
partition_for_policy(&slots, policy).is_empty(),
"{policy:?} committed for a hole"
);
}
}
#[test]
fn combined_is_one_group_of_every_engaged_slot() {
let slots = [engaged(false), HOLE, engaged(true), engaged(false)];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::Combined),
vec![vec![0, 2, 3]],
"the modesetting slot goes in the same commit as the steady ones, \
which is what Combined means and what makes the frame atomic"
);
}
#[test]
fn per_crtc_is_one_group_per_engaged_slot() {
let slots = [engaged(false), HOLE, engaged(true)];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::PerCrtc),
vec![vec![0], vec![2]],
"one commit each, and the hole between them is not one of them"
);
}
#[test]
fn auto_keeps_a_uniformly_steady_set_together() {
let slots = [engaged(false), engaged(false), HOLE];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::AutoOnModeset),
vec![vec![0, 1]]
);
}
#[test]
fn auto_keeps_a_uniformly_modesetting_set_together() {
let slots = [engaged(true), engaged(true)];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::AutoOnModeset),
vec![vec![0, 1]]
);
}
#[test]
fn auto_splits_a_mixed_set_with_the_modeset_first() {
let slots = [engaged(false), engaged(true), engaged(false), engaged(true)];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::AutoOnModeset),
vec![vec![1, 3], vec![0, 2]],
"modesetting slots first, steady ones second, each in index order"
);
}
#[test]
fn holes_do_not_make_a_uniform_set_look_mixed() {
let slots = [HOLE, engaged(true), HOLE];
assert_eq!(
partition_for_policy(&slots, NarrowPolicy::AutoOnModeset),
vec![vec![1]],
"one modesetting output and two holes is uniform, not mixed"
);
}
fn empty_set() -> SceneSet {
SceneSet::new(Vec::new())
}
fn source() -> std::rc::Rc<std::cell::RefCell<dyn drmkit_scene::LayerBufferSource>> {
std::rc::Rc::new(std::cell::RefCell::new(Stub))
}
struct Stub;
impl drmkit_scene::LayerBufferSource for Stub {
fn acquire(&mut self) -> Result<drmkit_scene::AcquiredBuffer, drmkit_scene::SourceError> {
Err(drmkit_scene::SourceError::WouldBlock)
}
fn release(&mut self, _acquired: drmkit_scene::AcquiredBuffer) {}
fn binding_model(&self) -> drmkit_scene::BindingModel {
drmkit_scene::BindingModel::SceneSubmitsFbId
}
fn format(&self) -> drmkit_scene::SourceFormat {
drmkit_scene::SourceFormat {
fourcc: drmkit_fmt::fourcc::XRGB8888,
modifier: 0,
width: 64,
height: 64,
}
}
}
#[test]
fn a_set_with_no_scenes_is_constructible() {
let set = empty_set();
assert_eq!(set.slot_count(), 0);
assert_eq!(set.scene_count(), 0);
assert!(
set.plan_commits(&[], NarrowPolicy::AutoOnModeset)
.is_empty()
);
}
#[test]
fn an_index_out_of_range_answers_nothing() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
assert!(set.scene(0).is_some());
assert!(set.scene(1).is_none());
assert!(set.scene(usize::MAX).is_none());
assert!(set.scene_mut(99).is_none());
}
#[test]
fn a_layer_with_no_targets_is_refused() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
let error = set
.add_layer(&LayerSpec {
source: source(),
targets: Vec::new(),
})
.expect_err("a layer that appears nowhere is a caller bug, not a no-op");
assert_eq!(error, SetError::NoTargets);
}
#[test]
fn a_target_naming_no_scene_is_refused() {
let mut set = empty_set();
let error = set
.add_layer(&LayerSpec {
source: source(),
targets: vec![Target {
scene_index: 0,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
}],
})
.expect_err("there is no scene 0");
assert_eq!(error, SetError::NoSuchScene { index: 0, count: 0 });
}
#[test]
fn a_spec_with_one_bad_target_leaves_the_set_untouched() {
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
]);
let error = set
.add_layer(&LayerSpec {
source: source(),
targets: vec![
Target {
scene_index: 0,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
},
Target {
scene_index: 7,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
},
],
})
.expect_err("scene 7 does not exist");
assert_eq!(error, SetError::NoSuchScene { index: 7, count: 2 });
assert_eq!(
set.scene(0).map(drmkit_scene::LayerScene::len),
Some(0),
"the good target must not have been added before the bad one was found"
);
}
#[test]
fn a_mirrored_layer_reaches_every_target_and_leaves_together() {
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
]);
let handle = set
.add_layer(&LayerSpec {
source: source(),
targets: vec![
Target {
scene_index: 0,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
},
Target {
scene_index: 1,
display: drmkit_scene::DisplayParams::default(),
force_composited: true,
},
],
})
.expect("both scenes exist");
assert!(handle.is_valid());
assert_eq!(set.scene(0).map(drmkit_scene::LayerScene::len), Some(1));
assert_eq!(set.scene(1).map(drmkit_scene::LayerScene::len), Some(1));
let composited = |index: usize| {
set.scene(index)
.and_then(|scene| scene.handles().next())
.and_then(|handle| set.scene(index)?.layer(handle))
.map(drmkit_scene::SceneLayer::is_force_composited)
};
assert_eq!(composited(0), Some(false));
assert_eq!(
composited(1),
Some(true),
"targets carry their own flags; one setting it must not set the other"
);
set.remove_layer(handle);
assert_eq!(
set.scene(0).map(drmkit_scene::LayerScene::len),
Some(0),
"removing a mirrored layer removes every copy, or the outputs that \
keep theirs go on scanning out content the caller has forgotten"
);
assert_eq!(set.scene(1).map(drmkit_scene::LayerScene::len), Some(0));
}
#[test]
fn removing_with_a_stale_or_default_handle_is_a_no_op() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
let handle = set
.add_layer(&LayerSpec {
source: source(),
targets: vec![Target {
scene_index: 0,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
}],
})
.expect("scene 0 exists");
set.remove_layer(SetLayerHandle::default());
assert_eq!(
set.scene(0).map(drmkit_scene::LayerScene::len),
Some(1),
"a default handle is not a license to remove layer one"
);
set.remove_layer(handle);
assert_eq!(set.scene(0).map(drmkit_scene::LayerScene::len), Some(0));
set.remove_layer(handle);
}
fn one_target() -> Vec<Target> {
vec![Target {
scene_index: 0,
display: drmkit_scene::DisplayParams::default(),
force_composited: false,
}]
}
#[test]
fn a_removed_layers_slot_is_reused_and_its_old_handle_is_stale() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
let spec = LayerSpec {
source: source(),
targets: one_target(),
};
let first = set.add_layer(&spec).expect("scene 0 exists");
for _ in 0..8 {
let handle = set.add_layer(&spec).expect("scene 0 exists");
set.remove_layer(handle);
}
assert_eq!(set.layers.len(), 2, "two live at most, so two slots");
set.remove_layer(first);
let second = set.add_layer(&spec).expect("scene 0 exists");
assert_ne!(second, first, "a reused slot carries a new generation");
set.remove_layer(first);
assert_eq!(
set.scene(0).map(drmkit_scene::LayerScene::len),
Some(1),
"the old handle must not remove the layer that took its slot"
);
}
#[derive(Default)]
struct Binding {
bound: Vec<u32>,
unbound: Vec<u32>,
}
impl drmkit_scene::LayerBufferSource for Binding {
fn acquire(&mut self) -> Result<drmkit_scene::AcquiredBuffer, drmkit_scene::SourceError> {
Err(drmkit_scene::SourceError::WouldBlock)
}
fn release(&mut self, _acquired: drmkit_scene::AcquiredBuffer) {}
fn format(&self) -> drmkit_scene::SourceFormat {
Stub.format()
}
fn bind_to_plane(&mut self, plane_id: u32) -> Result<(), drmkit_scene::SourceError> {
self.bound.push(plane_id);
Ok(())
}
fn unbind_from_plane(&mut self, plane_id: u32) {
self.unbound.push(plane_id);
}
}
#[test]
fn plane_binding_reaches_the_shared_source() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
let shared = std::rc::Rc::new(std::cell::RefCell::new(Binding::default()));
set.add_layer(&LayerSpec {
source: shared.clone(),
targets: one_target(),
})
.expect("scene 0 exists");
let scene = set.scene_mut(0).expect("scene 0");
let handle = scene.handles().next().expect("the copy");
let layer = scene.layer_mut(handle).expect("the copy");
layer.source_mut().bind_to_plane(31).expect("bind");
layer.source_mut().unbind_from_plane(31);
assert_eq!(shared.borrow().bound, [31]);
assert_eq!(shared.borrow().unbound, [31]);
}
#[test]
fn removing_a_scene_leaves_a_hole_rather_than_renumbering() {
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
drmkit_scene::LayerScene::new(3),
]);
set.remove_scene(1);
assert_eq!(set.slot_count(), 3, "the slot stays");
assert_eq!(set.scene_count(), 2, "but it holds nothing");
assert!(set.scene(1).is_none());
assert_eq!(
set.scene(2).map(drmkit_scene::LayerScene::crtc_id),
Some(3),
"scene 2 is still scene 2"
);
}
#[test]
fn removing_a_scene_that_is_not_there_is_a_no_op() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
set.remove_scene(99);
set.remove_scene(0);
set.remove_scene(0);
assert_eq!(set.scene_count(), 0);
assert_eq!(set.slot_count(), 1);
}
#[test]
fn a_new_scene_fills_a_hole_before_growing_the_set() {
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
]);
set.remove_scene(0);
let index = set.add_scene(drmkit_scene::LayerScene::new(3));
assert_eq!(index, 0, "the hole was reused");
assert_eq!(set.slot_count(), 2, "and the set did not grow");
assert_eq!(set.scene(0).map(drmkit_scene::LayerScene::crtc_id), Some(3));
}
#[test]
fn a_new_scene_with_no_hole_to_fill_goes_on_the_end() {
let mut set = SceneSet::new(vec![drmkit_scene::LayerScene::new(1)]);
assert_eq!(set.add_scene(drmkit_scene::LayerScene::new(2)), 1);
assert_eq!(set.slot_count(), 2);
}
#[test]
fn the_commit_plan_follows_the_set_and_what_the_caller_says_needs_a_modeset() {
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
drmkit_scene::LayerScene::new(3),
]);
set.remove_scene(1);
assert_eq!(
set.plan_commits(&[], NarrowPolicy::AutoOnModeset),
vec![vec![0, 2]],
"nothing is modesetting, so the two engaged outputs stay atomic"
);
assert_eq!(
set.plan_commits(&[2], NarrowPolicy::AutoOnModeset),
vec![vec![2], vec![0]],
"one modesetting and one steady is mixed, and the modeset leads"
);
assert_eq!(
set.plan_commits(&[1], NarrowPolicy::AutoOnModeset),
vec![vec![0, 2]],
"a modeset named on a hole changes nothing -- there is no output there"
);
}
#[test]
#[ignore = "needs a DRM device; run under the vkms lane with --include-ignored"]
fn a_mirrored_copy_can_be_read_for_composition_vkms() {
use drmkit_dumb::MapAccess;
use drmkit_scene::LayerBufferSource as _;
let path = std::env::var("DRMKIT_TEST_CARD").unwrap_or_else(|_| "/dev/dri/card0".to_owned());
let device = match drmkit_core::Device::open(&path) {
Ok(device) => device,
Err(error) => {
assert!(
std::env::var_os("DRMKIT_REQUIRE_MASTER").is_none(),
"{path}: {error}, but DRMKIT_REQUIRE_MASTER is set"
);
drmkit_testkit::skipped(&format!("no DRM device at {path} ({error})"));
return;
}
};
let mut painted = drmkit_scene_sources::DumbBufferSource::create(
&device,
16,
16,
drmkit_fmt::fourcc::ARGB8888,
)
.expect("a 16x16 dumb buffer");
painted
.map(MapAccess::Write)
.expect("a dumb buffer maps")
.pixels_mut()
.fill(0x5a);
let shared: std::rc::Rc<std::cell::RefCell<dyn drmkit_scene::LayerBufferSource>> =
std::rc::Rc::new(std::cell::RefCell::new(painted));
let mut set = SceneSet::new(vec![
drmkit_scene::LayerScene::new(1),
drmkit_scene::LayerScene::new(2),
]);
set.add_layer(&LayerSpec {
source: shared,
targets: (0..2)
.map(|scene_index| Target {
scene_index,
display: drmkit_scene::DisplayParams::default(),
force_composited: true,
})
.collect(),
})
.expect("both scenes exist");
for index in 0..2 {
let scene = set.scene_mut(index).expect("the scene");
let handle = scene.handles().next().expect("its copy");
let layer = scene.layer_mut(handle).expect("its copy");
let mut seen = Vec::new();
layer
.source_mut()
.with_mapping(MapAccess::Read, &mut |mapping| {
seen.extend_from_slice(mapping.row(0).unwrap_or_default());
})
.unwrap_or_else(|error| panic!("scene {index}: the copy cannot be read: {error}"));
assert_eq!(seen.len(), 16 * 4, "scene {index}: one row of ARGB8888");
assert!(seen.iter().all(|byte| *byte == 0x5a), "scene {index}");
}
}