use std::sync::Mutex;
use drm::Device as _;
use drm::control::Device as _;
use drmkit_core::{Device, ObjectType, PropertyStore};
use drmkit_planes::{PlaneRegistry, PlaneType};
static CARD_LOCK: Mutex<()> = Mutex::new(());
fn card_guard() -> std::sync::MutexGuard<'static, ()> {
CARD_LOCK
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
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;
}
};
device.enable_universal_planes().expect("universal planes");
device.enable_atomic().expect("atomic");
Some(device)
}
#[test]
#[ignore = "needs a DRM device"]
fn every_plane_the_kernel_lists_is_read() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let expected = device.plane_handles().expect("planes").len();
let registry = PlaneRegistry::probe(&device).expect("probe");
assert_eq!(registry.all().len(), expected);
assert!(expected > 0, "a device with no planes cannot display");
if let Ok(minimum) = std::env::var("DRMKIT_MIN_PLANES") {
let minimum: usize = minimum.parse().expect("DRMKIT_MIN_PLANES is a number");
let resources = device.resource_handles().expect("resources");
let (_, crtc_index) = drmkit_testkit::crtc::pick_crtc(&device, &resources);
let candidates = registry.for_crtc(crtc_index).count();
assert!(
candidates >= minimum,
"CRTC index {crtc_index} has {candidates} candidate plane(s) and \
DRMKIT_MIN_PLANES asks for {minimum} (the device lists {expected} \
in total, which is not what the allocator gets to use)"
);
}
assert!(
registry
.all()
.iter()
.any(|plane| plane.plane_type == PlaneType::Primary),
"no primary plane: was DRM_CLIENT_CAP_UNIVERSAL_PLANES set?"
);
}
#[test]
#[ignore = "needs a DRM device"]
fn a_probe_agrees_with_the_property_set() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let registry = PlaneRegistry::probe(&device).expect("probe");
for plane in registry.all() {
let mut properties = PropertyStore::new();
properties
.cache_properties(&device, plane.id, ObjectType::Plane)
.expect("cache");
let present = |name: &str| properties.property_id(plane.id, name).is_ok();
assert_eq!(plane.supports_rotation, present("rotation"), "{}", plane.id);
assert_eq!(
plane.has_pixel_blend_mode,
present("pixel blend mode"),
"{}",
plane.id
);
assert_eq!(plane.has_per_plane_alpha, present("alpha"), "{}", plane.id);
assert_eq!(
plane.has_color_encoding,
present("COLOR_ENCODING"),
"{}",
plane.id
);
assert_eq!(
plane.has_color_range,
present("COLOR_RANGE"),
"{}",
plane.id
);
if plane.has_pixel_blend_mode {
assert_eq!(
plane.blend_modes.premultiplied,
properties
.enum_value(&device, plane.id, "pixel blend mode", "Pre-multiplied")
.ok(),
"{}",
plane.id
);
}
if plane.has_color_encoding {
assert_eq!(
plane.color_encodings.bt709,
properties
.enum_value(&device, plane.id, "COLOR_ENCODING", "ITU-R BT.709 YCbCr")
.ok(),
"{}",
plane.id
);
}
}
}
#[test]
#[ignore = "needs a DRM device"]
fn a_planes_crtc_mask_matches_what_the_kernel_says() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let resources = device.resource_handles().expect("resources");
let registry = PlaneRegistry::probe(&device).expect("probe");
for plane in registry.all() {
let handle = drm::control::plane::Handle::from(
drm::control::RawResourceHandle::new(plane.id).expect("a plane id is not zero"),
);
let info = device.get_plane(handle).expect("plane");
let allowed = resources.filter_crtcs(info.possible_crtcs());
for (index, crtc) in resources.crtcs().iter().enumerate() {
let index = u32::try_from(index).expect("a CRTC index fits in a u32");
assert_eq!(
plane.compatible_with_crtc(index),
allowed.contains(crtc),
"plane {} against CRTC index {index}",
plane.id
);
}
}
}
#[test]
#[ignore = "needs a DRM device"]
fn a_rotation_capable_plane_reports_which_rotations() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let registry = PlaneRegistry::probe(&device).expect("probe");
let mut rotatable = 0;
for plane in registry.all() {
if !plane.supports_rotation {
assert_eq!(plane.rotation_bits, 0, "plane {}", plane.id);
continue;
}
rotatable += 1;
let advertised = rotation_entry_count(&device, plane.id);
assert_eq!(
plane.rotation_bits.count_ones(),
advertised,
"plane {} advertises {advertised} rotations but reports {:#x}",
plane.id,
plane.rotation_bits
);
assert_eq!(
plane.rotation_bits & !0x3f,
0,
"plane {} advertises a bit outside DRM_MODE_ROTATE/REFLECT: {:#x}",
plane.id,
plane.rotation_bits
);
println!(
"plane {} rotation bits {:#x}",
plane.id, plane.rotation_bits
);
}
println!("{rotatable} of {} planes rotate", registry.all().len());
}
#[test]
#[ignore = "needs a DRM device"]
fn a_plane_without_in_formats_still_has_a_format_table() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let registry = PlaneRegistry::probe(&device).expect("probe");
for plane in registry.all() {
assert!(
!plane.formats.is_empty(),
"plane {} advertises no format at all",
plane.id
);
assert!(
!plane.format_table.is_empty(),
"plane {} has no queryable format table",
plane.id
);
if !plane.has_format_modifiers {
println!("plane {}: no IN_FORMATS, LINEAR synthesized", plane.id);
}
}
}
fn rotation_entry_count(device: &Device, plane_id: u32) -> u32 {
use std::os::fd::AsFd as _;
let mut properties = PropertyStore::new();
properties
.cache_properties(device, plane_id, ObjectType::Plane)
.expect("cache");
let id = properties
.property_id(plane_id, "rotation")
.expect("the caller checked it is there");
let mut values = Vec::new();
let mut enums = Vec::new();
drm_ffi::mode::get_property(device.as_fd(), id, Some(&mut values), Some(&mut enums))
.expect("get_property");
u32::try_from(enums.len()).expect("a property has few entries")
}
#[test]
#[ignore = "needs a DRM device"]
fn cursor_planes_carry_the_devices_cursor_caps() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let cap = |cap| {
device
.get_driver_capability(cap)
.ok()
.and_then(|value| u32::try_from(value).ok())
.unwrap_or(0)
};
let caps = (
cap(drm::DriverCapability::CursorWidth),
cap(drm::DriverCapability::CursorHeight),
);
let registry = PlaneRegistry::probe(&device).expect("probe");
let mut cursors = 0;
for plane in registry.all() {
let carried = (plane.cursor_max_w, plane.cursor_max_h);
if plane.plane_type == PlaneType::Cursor {
cursors += 1;
assert_eq!(carried, caps, "cursor plane {}", plane.id);
} else {
assert_eq!(carried, (0, 0), "plane {} is not a cursor", plane.id);
}
}
if cursors == 0 {
drmkit_testkit::skipped("no cursor plane on this device (vkms: enable_cursor=1)");
}
}