use std::os::fd::{FromRawFd as _, OwnedFd};
use std::sync::LazyLock;
use std::sync::atomic::{AtomicBool, Ordering};
use drm::control::{AtomicCommitFlags, Device as ControlDevice, atomic::AtomicModeReq};
use crate::device::Device;
use crate::error::{CoreError, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[allow(
clippy::exhaustive_structs,
reason = "a value defined by its fields; there is nothing to add to it"
)]
pub struct PropertyWrite {
pub object_id: u32,
pub property_id: u32,
pub value: u64,
}
#[derive(Debug, Clone, Default)]
pub struct AtomicRequest {
writes: Vec<PropertyWrite>,
}
impl AtomicRequest {
#[must_use]
pub const fn new() -> Self {
Self { writes: Vec::new() }
}
#[must_use]
pub fn with_capacity(capacity: usize) -> Self {
Self {
writes: Vec::with_capacity(capacity),
}
}
pub fn add_property(&mut self, object_id: u32, property_id: u32, value: u64) -> Result<()> {
if object_id == 0 || property_id == 0 {
return Err(CoreError::Io(rustix::io::Errno::INVAL));
}
self.writes.push(PropertyWrite {
object_id,
property_id,
value,
});
Ok(())
}
#[must_use]
pub fn writes(&self) -> &[PropertyWrite] {
&self.writes
}
#[must_use]
pub fn len(&self) -> usize {
self.writes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.writes.is_empty()
}
pub fn clear(&mut self) {
self.writes.clear();
}
fn build(&self) -> Result<AtomicModeReq> {
let mut req = AtomicModeReq::new();
for write in &self.writes {
let (Some(object), Some(property)) = (
drm::control::RawResourceHandle::new(write.object_id),
drm::control::RawResourceHandle::new(write.property_id),
) else {
return Err(CoreError::Io(rustix::io::Errno::INVAL));
};
req.add_raw_property(object, property.into(), write.value);
}
Ok(req)
}
pub fn test(&self, device: &Device, flags: AtomicCommitFlags) -> Result<()> {
let test_flags =
(flags - AtomicCommitFlags::PAGE_FLIP_EVENT) | AtomicCommitFlags::TEST_ONLY;
let result = device
.atomic_commit(test_flags, self.build()?)
.map_err(|e| CoreError::from_io(&e));
self.trace(device, &result, "test");
result
}
pub fn commit(&self, device: &Device, flags: AtomicCommitFlags) -> Result<()> {
let result = device
.atomic_commit(flags, self.build()?)
.map_err(|e| CoreError::from_io(&e));
self.trace(device, &result, "commit");
result
}
pub fn dump(&self, device: &Device, why: &str) {
if !*ATOMIC_DEBUG {
return;
}
let name_of = |property_id: u32| {
let handle = drm::control::property::Handle::from(
drm::control::RawResourceHandle::new(property_id)?,
);
let info = device.get_property(handle).ok()?;
Some(info.name().to_string_lossy().into_owned())
};
for line in self.dump_lines(why, name_of) {
drmkit_log::log_channel!(drmkit_log::LogLevel::Debug, "{line}");
}
}
pub(crate) fn dump_lines(
&self,
why: &str,
name_of: impl Fn(u32) -> Option<String>,
) -> Vec<String> {
let count = self.writes.len();
let mut lines = Vec::with_capacity(count + 1);
lines.push(format!(
"[atomic] {why}: {count} propert{} in this request",
if count == 1 { "y" } else { "ies" }
));
for write in &self.writes {
let name = name_of(write.property_id).unwrap_or_else(|| "?".to_owned());
lines.push(format!(
"[atomic] obj={:<4} {name:<24} = {}",
write.object_id, write.value
));
}
lines
}
fn trace(&self, device: &Device, result: &Result<()>, what: &str) {
if !*ATOMIC_DEBUG {
return;
}
match result {
Ok(()) => {
if SAW_REJECTION.swap(false, Ordering::Relaxed) {
self.dump(
device,
&format!("{what} accepted (first after a rejection)"),
);
}
}
Err(CoreError::NotMaster) => {}
Err(error) => {
SAW_REJECTION.store(true, Ordering::Relaxed);
self.dump(device, &format!("{what} rejected ({error})"));
}
}
}
}
static ATOMIC_DEBUG: LazyLock<bool> = LazyLock::new(|| {
std::env::var_os("DRM_ATOMIC_DEBUG").is_some() || std::env::var_os("DRM_ALLOC_DEBUG").is_some()
});
static SAW_REJECTION: AtomicBool = AtomicBool::new(false);
pub fn commit_with_out_fence<F, K>(
device: &Device,
flags: AtomicCommitFlags,
build: F,
) -> Result<Option<OwnedFd>>
where
F: FnOnce(&mut AtomicRequest, u64) -> Result<K>,
{
let mut slot: i32 = -1;
let mut request = AtomicRequest::new();
let kept = build(&mut request, (&raw mut slot) as u64)?;
request.commit(device, flags)?;
drop(kept);
if slot < 0 {
return Ok(None);
}
Ok(Some(unsafe { OwnedFd::from_raw_fd(slot) }))
}