use std::sync::Mutex;
use std::os::fd::AsRawFd as _;
use drmkit_core::Device;
use drmkit_fmt::fourcc;
use super::{GbmBuffer, GbmDevice, GbmError};
const INVALID_MODIFIER: u64 = (1 << 56) - 1;
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());
match Device::open(&path) {
Ok(device) => Some(device),
Err(error) => {
assert!(
std::env::var_os("DRMKIT_REQUIRE_MASTER").is_none(),
"{path}: {error}, but DRMKIT_REQUIRE_MASTER is set"
);
println!("note: skipped -- no DRM device at {path} ({error})");
None
}
}
}
#[test]
fn a_drm_node_opens_as_a_gbm_device() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("open a GBM device");
println!("note: gbm backend is {}", gbm.backend_name());
}
#[test]
fn a_buffer_reports_its_geometry() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let buffer = GbmBuffer::create(&gbm, 64, 32, fourcc::ARGB8888).expect("allocate");
assert_eq!(buffer.width(), 64);
assert_eq!(buffer.height(), 32);
assert_eq!(buffer.fourcc(), fourcc::ARGB8888);
assert!(
buffer.stride() >= 64 * 4,
"a stride below the row's own width cannot be right; got {}",
buffer.stride()
);
assert!(buffer.plane_count() >= 1);
}
#[test]
fn a_buffer_exports_a_dma_buf() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let buffer = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate");
let first = buffer.export().expect("export a dma-buf");
let second = buffer.export().expect("export again");
assert!(first.as_raw_fd() >= 0);
assert_ne!(
first.as_raw_fd(),
second.as_raw_fd(),
"each export must hand back its own descriptor; sharing one would mean \
closing either invalidates the other"
);
}
#[test]
fn a_buffer_outlives_its_device() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let buffer = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate");
drop(gbm);
buffer
.export()
.expect("the buffer's descriptor must outlive the GbmDevice it came from");
}
#[test]
fn a_surface_outlives_its_device() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let surface = gbm
.create_surface(64, 64, fourcc::XRGB8888, &[])
.expect("a surface");
let raw = surface.as_raw();
drop(gbm);
assert!(
!raw.is_null(),
"the surface pointer an EGL producer would take"
);
drop(surface);
}
#[test]
fn a_bare_descriptor_opens_as_a_gbm_device() {
let _guard = card_guard();
let path = std::env::var("DRMKIT_TEST_CARD").unwrap_or_else(|_| "/dev/dri/card0".to_owned());
let Ok(file) = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(&path)
else {
println!("note: skipped -- no DRM device at {path}");
return;
};
let gbm = GbmDevice::from_fd(&file).expect("open a GBM device from a bare descriptor");
let buffer = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate");
drop(file);
assert!(
buffer.handle() != 0,
"a GEM handle on the duplicated descriptor"
);
buffer
.export()
.expect("the duplicate, not the caller's descriptor, holds the buffer");
}
#[test]
fn a_buffer_reports_a_modifier() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let buffer = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate");
let modifier = buffer.modifier();
println!("note: modifier is {modifier:#x}");
let again = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate again");
assert_eq!(
again.modifier(),
modifier,
"the same request to the same device must describe the same layout"
);
assert_eq!(again.stride(), buffer.stride(), "and the same stride");
}
#[test]
fn an_unknown_format_is_refused() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let refused = GbmBuffer::create(&gbm, 64, 64, 0xDEAD_BEEF);
assert!(
matches!(refused, Err(GbmError::Allocation(_))),
"a format no driver knows must be refused at allocation"
);
}
#[test]
fn a_constrained_allocation_comes_back_in_a_listed_modifier() {
const LINEAR: u64 = 0;
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let buffer = GbmBuffer::create_with_modifiers(&gbm, 64, 64, fourcc::ARGB8888, &[LINEAR])
.expect("allocate LINEAR");
assert!(
matches!(buffer.modifier(), LINEAR | INVALID_MODIFIER),
"the driver was offered LINEAR and reported {:#x}",
buffer.modifier()
);
assert_eq!(buffer.width(), 64);
assert_eq!(buffer.height(), 64);
assert!(buffer.stride() >= 64 * 4);
}
#[test]
fn an_empty_modifier_list_allocates_rather_than_refusing() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let constrained = GbmBuffer::create_with_modifiers(&gbm, 64, 64, fourcc::ARGB8888, &[])
.expect("an empty list is no constraint");
let plain = GbmBuffer::create(&gbm, 64, 64, fourcc::ARGB8888).expect("allocate");
assert_eq!(
constrained.modifier(),
plain.modifier(),
"an empty list must take the same path as an unconstrained allocation"
);
}
#[test]
fn an_unknown_format_is_refused_on_the_constrained_path() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let refused = GbmBuffer::create_with_modifiers(&gbm, 64, 64, 0xDEAD_BEEF, &[0]);
assert!(
matches!(refused, Err(GbmError::Allocation(_))),
"the modifier fallback must not turn an unsupported format into a buffer"
);
}
#[test]
fn every_honored_layout_comes_back_as_asked() {
use drmkit_fmt::{mod_code, vendor};
let _guard = card_guard();
let Some(device) = open_card() else { return };
let gbm = GbmDevice::new(&device).expect("gbm device");
let mut candidates = vec![0];
for vendor in [
vendor::INTEL,
vendor::SAMSUNG,
vendor::QCOM,
vendor::VIVANTE,
vendor::BROADCOM,
vendor::ALLWINNER,
] {
candidates.extend((1..=8).map(|body| mod_code(vendor, body)));
}
let mut honored = Vec::new();
let mut substituted = std::collections::BTreeSet::new();
let mut linear_reported = None;
for &asked in &candidates {
let Ok(buffer) = GbmBuffer::create_with_modifiers(&gbm, 64, 64, fourcc::ARGB8888, &[asked])
else {
continue;
};
let got = buffer.modifier();
if asked == 0 {
linear_reported = Some(got);
}
if got == asked {
honored.push(asked);
} else {
substituted.insert(got);
}
}
println!("note: honored layouts {honored:x?}, substitutes seen {substituted:x?}");
match linear_reported {
Some(INVALID_MODIFIER) => println!("note: this driver reports no modifiers"),
reported => assert_eq!(
reported,
Some(0),
"every driver can allocate linear, and asking for it alone must get it"
),
}
if let Ok(minimum) = std::env::var("DRMKIT_MIN_LAYOUTS") {
let minimum: usize = minimum.parse().expect("DRMKIT_MIN_LAYOUTS is a number");
assert!(
honored.len() >= minimum,
"{} layout(s) came back as asked and DRMKIT_MIN_LAYOUTS asks for {minimum}: \
either the modifier list is not reaching the driver or the modifier \
read back is not the driver's",
honored.len()
);
}
}
#[test]
fn the_hidden_entry_points_are_the_missing_ones() {
let Ok(gbm) = crate::ffi::gbm() else {
println!("note: skipped -- no libgbm to open");
return;
};
for name in crate::ffi::OPTIONAL {
assert!(
gbm.has(name).is_some(),
"{name} is hideable but the loader does not report it"
);
}
let hidden = std::env::var(crate::HIDE_ENV).unwrap_or_default();
for name in hidden
.split(',')
.map(str::trim)
.filter(|name| !name.is_empty())
{
assert!(
crate::ffi::OPTIONAL.contains(&name),
"{name} is not an entry point that can be hidden"
);
assert_eq!(gbm.has(name), Some(false), "{name} was asked hidden");
}
let shape: Vec<_> = crate::ffi::OPTIONAL
.iter()
.map(|name| format!("{name}={}", gbm.has(name) == Some(true)))
.collect();
println!("note: libgbm as seen: {}", shape.join(" "));
}