use std::sync::{Mutex, MutexGuard};
use drmkit_core::Device;
use drmkit_fmt::fourcc;
use drmkit_scene::{LayerBufferSource, SourceError};
use crate::{GbmSurfaceSource, SurfaceConfig, SurfaceError};
static CARD_LOCK: Mutex<()> = Mutex::new(());
fn card_guard() -> 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);
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"
);
drmkit_testkit::skipped(&format!("no DRM device at {path} ({error})"));
None
}
}
}
fn good_config() -> SurfaceConfig {
SurfaceConfig {
width: 64,
height: 64,
fourcc: fourcc::XRGB8888,
modifier: None,
require_bind: false,
}
}
#[test]
fn a_zero_dimension_or_format_is_refused_by_name() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
for (field, config) in [
(
"width",
SurfaceConfig {
width: 0,
..good_config()
},
),
(
"height",
SurfaceConfig {
height: 0,
..good_config()
},
),
(
"fourcc",
SurfaceConfig {
fourcc: 0,
..good_config()
},
),
] {
let error = GbmSurfaceSource::create(&device, &config)
.expect_err("a zero {field} is not a surface");
assert!(
matches!(error, SurfaceError::Invalid { field: named } if named == field),
"asked about {field} and got {error}"
);
}
}
#[test]
fn a_format_gbm_does_not_know_is_refused_before_the_driver_sees_it() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let error = GbmSurfaceSource::create(
&device,
&SurfaceConfig {
fourcc: 0xDEAD_BEEF,
..good_config()
},
)
.expect_err("no driver knows that format");
assert!(
matches!(error, SurfaceError::UnsupportedFormat { fourcc } if fourcc == 0xDEAD_BEEF),
"got {error}, which does not say the format was the problem"
);
}
#[test]
fn a_surface_reports_the_shape_it_was_asked_for() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(source) = GbmSurfaceSource::create(&device, &good_config()) else {
drmkit_testkit::skipped("no working GBM surface backend on this card");
return;
};
let format = LayerBufferSource::format(&source);
assert_eq!(format.width, 64);
assert_eq!(format.height, 64);
assert_eq!(format.fourcc, fourcc::XRGB8888);
}
#[test]
fn a_surface_has_no_cpu_mapping() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(mut source) = GbmSurfaceSource::create(&device, &good_config()) else {
drmkit_testkit::skipped("no working GBM surface backend on this card");
return;
};
assert!(matches!(
source.map(drmkit_dumb::MapAccess::Read),
Err(SourceError::Unsupported)
));
}
#[test]
fn releasing_an_unknown_token_is_ignored() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(mut source) = GbmSurfaceSource::create(&device, &good_config()) else {
drmkit_testkit::skipped("no working GBM surface backend on this card");
return;
};
source.release(drmkit_scene::AcquiredBuffer {
fb_id: 0,
token: 42,
acquire_fence: None,
damage: Vec::new(),
});
source.release(drmkit_scene::AcquiredBuffer::default());
}
#[test]
fn pausing_the_session_stops_the_source_handing_anything_out() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(mut source) = GbmSurfaceSource::create(&device, &good_config()) else {
drmkit_testkit::skipped("no working GBM surface backend on this card");
return;
};
source.on_session_paused();
assert!(
matches!(source.acquire(), Err(SourceError::WouldBlock)),
"a paused source has no frame; reporting a real failure would abort \
the commit rather than skipping the layer"
);
}
#[test]
fn resuming_rebuilds_the_surface_against_the_new_device() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let Ok(mut source) = GbmSurfaceSource::create(&device, &good_config()) else {
drmkit_testkit::skipped("no working GBM surface backend on this card");
return;
};
let before = LayerBufferSource::format(&source);
source.on_session_paused();
source
.on_session_resumed(&device)
.expect("the same device is a device it can resume onto");
assert_eq!(
LayerBufferSource::format(&source),
before,
"callers rely on the shape surviving a resume"
);
}
#[test]
fn require_bind_gates_acquire_until_bound() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let config = SurfaceConfig {
require_bind: true,
..good_config()
};
let Ok(mut source) = GbmSurfaceSource::create(&device, &config) else {
drmkit_testkit::skipped("no GBM surface backend on this card");
return;
};
assert!(
matches!(source.acquire(), Err(SourceError::WouldBlock)),
"nothing has bound the surface, so locking its front buffer would crash"
);
source.mark_bound();
source.on_session_paused();
source
.on_session_resumed(&device)
.expect("the same device is a device it can resume onto");
assert!(
matches!(source.acquire(), Err(SourceError::WouldBlock)),
"the resume rebuilt the surface, and the producer has not bound the \
new one"
);
}
#[test]
fn the_acquire_fence_is_handed_out_once() {
let _guard = card_guard();
let Some(device) = open_card() else { return };
let mut source = match GbmSurfaceSource::create(&device, &good_config()) {
Ok(source) => source,
Err(error) => {
drmkit_testkit::skipped(&format!("no GBM surface here ({error})"));
return;
}
};
let fd = std::fs::File::open("/dev/null").expect("open /dev/null");
source.set_acquire_fence(drmkit_sync::SyncFence::from_owned(fd.into()));
assert!(
source.take_acquire_fence().is_some(),
"the fence is handed out"
);
assert!(
source.take_acquire_fence().is_none(),
"and only once: the next frame has no fence of its own"
);
}
#[test]
fn a_refused_framebuffer_keeps_its_errno() {
use rustix::io::Errno;
assert!(matches!(
crate::acquire_error(&SurfaceError::Framebuffer(Errno::NOSPC)),
SourceError::Failed(Errno::NOSPC)
));
assert!(matches!(
crate::acquire_error(&SurfaceError::UnsupportedFormat { fourcc: 0 }),
SourceError::Failed(Errno::INVAL)
));
}