use std::sync::Mutex;
use std::time::Duration;
use drm::control::Device as _;
use drmkit_core::Device;
use drmkit_cursor::{Cursor, Frame};
use drmkit_dumb::MapAccess;
use drmkit_scene::LayerBufferSource as _;
use drmkit_scene_sources::CursorSource;
static CARD_LOCK: Mutex<()> = Mutex::new(());
fn card_guard() -> std::sync::MutexGuard<'static, ()> {
CARD_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
const W: u32 = 16;
const H: u32 = 16;
fn pattern() -> Vec<u32> {
(0..W * H)
.map(|i| 0xFF00_0000 | (i * 0x0001_0203))
.collect()
}
fn open_card() -> Option<Device> {
let path = std::env::var("DRMKIT_TEST_CARD").unwrap_or_else(|_| "/dev/dri/card0".to_owned());
drmkit_testkit::announce_card(&path);
let device = match 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 None;
}
};
let resources = device.resource_handles().ok()?;
if resources.crtcs().is_empty() {
drmkit_testkit::skipped(&format!("{path} has no CRTC"));
return None;
}
Some(device)
}
fn read_back(source: &mut CursorSource) -> Vec<u32> {
let (width, height) = source.size();
let mapping = source.map(MapAccess::Read).expect("map");
let mut out = Vec::with_capacity((width * height) as usize);
for row in 0..height {
let bytes = mapping.row(row).expect("row");
for column in 0..width as usize {
let at = column * 4;
out.push(u32::from_le_bytes(
bytes[at..at + 4].try_into().expect("four bytes"),
));
}
}
out
}
fn frame(width: u32, height: u32, color: u32, delay: Duration) -> Frame {
Frame {
pixels: vec![color; (width * height) as usize],
width,
height,
xhot: 0,
yhot: 0,
delay,
}
}
fn advance_to_second_frame(source: &mut CursorSource, color: u32) -> bool {
let deadline = std::time::Instant::now() + Duration::from_secs(5);
while std::time::Instant::now() < deadline {
source.acquire().expect("acquire");
if read_back(source)[0] == color {
return true;
}
std::thread::sleep(Duration::from_millis(5));
}
false
}
#[test]
#[ignore = "needs a DRM device"]
fn a_sprite_reaches_the_buffer_intact() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let pixels = pattern();
let mut source = CursorSource::from_argb(&device, &pixels, W, H, 1, 2).expect("cursor source");
assert_eq!(source.hotspot(), (1, 2));
assert_eq!(source.size(), (W, H));
let acquired = source.acquire().expect("acquire");
assert_ne!(
acquired.fb_id, 0,
"a cursor with no framebuffer cannot scan out"
);
assert_eq!(
read_back(&mut source),
pixels,
"the sprite came back changed"
);
}
#[test]
#[ignore = "needs a DRM device"]
fn the_buffer_is_sized_to_the_largest_frame() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let frames = vec![
frame(16, 16, 0xFF00_00FF, Duration::from_millis(50)),
frame(24, 20, 0xFF00_FF00, Duration::from_millis(50)),
];
let cursor = Cursor::from_frames(frames).expect("cursor");
let source = CursorSource::new(&device, cursor).expect("cursor source");
assert_eq!(source.size(), (24, 20));
}
#[test]
#[ignore = "needs a DRM device"]
fn a_smaller_frame_does_not_leave_the_previous_one_around_it() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let frames = vec![
frame(20, 20, 0xFFFF_0000, Duration::from_millis(20)),
frame(12, 12, 0xFF00_FF00, Duration::from_millis(20)),
];
let cursor = Cursor::from_frames(frames).expect("cursor");
let mut source = CursorSource::new(&device, cursor).expect("cursor source");
assert!(read_back(&mut source).iter().all(|px| *px == 0xFFFF_0000));
assert!(
advance_to_second_frame(&mut source, 0xFF00_FF00),
"the animation never advanced"
);
let pixels = read_back(&mut source);
assert_eq!(pixels[0], 0xFF00_FF00, "top-left is the new frame");
assert_eq!(
pixels[19 * 20 + 19],
0,
"bottom-right still carries the previous frame"
);
}
#[test]
#[ignore = "needs a DRM device"]
fn an_unchanged_frame_reports_no_damage() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let frames = vec![
frame(16, 16, 0xFFFF_0000, Duration::from_secs(3600)),
frame(16, 16, 0xFF00_FF00, Duration::from_secs(3600)),
];
let cursor = Cursor::from_frames(frames).expect("cursor");
let mut source = CursorSource::new(&device, cursor).expect("cursor source");
let first = source.acquire().expect("acquire");
let second = source.acquire().expect("acquire");
assert_eq!(first.fb_id, second.fb_id, "one buffer, handed out again");
assert!(
second.damage.is_empty(),
"nothing changed, nothing to report"
);
}
#[test]
#[ignore = "needs a DRM device"]
fn a_static_cursor_never_advances() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let pixels = pattern();
let mut source = CursorSource::from_argb(&device, &pixels, W, H, 0, 0).expect("cursor source");
assert!(!source.cursor().is_animated());
for _ in 0..4 {
source.acquire().expect("acquire");
}
assert_eq!(read_back(&mut source), pixels, "a static cursor repainted");
}