1
0
Fork 0
forked from wry/wry
wry/src/backends/metal/transaction.rs
2026-03-02 18:16:36 +01:00

1168 lines
46 KiB
Rust

use {
crate::{
allocator::BufferObject,
backend::{
BackendColorSpace, BackendConnectorState, BackendEotfs, BackendGammaLut, Connector,
ConnectorEvent,
transaction::{
BackendAppliedConnectorTransaction, BackendConnectorTransaction,
BackendConnectorTransactionError, BackendPreparedConnectorTransaction,
},
},
backends::metal::{
allocator::RenderBuffer,
video::{
FrontState, MetalConnector, MetalCrtc, MetalDrmDeviceData, MetalPlane, PlaneType,
},
},
format::{ARGB8888, Format},
gfx_api::SyncFile,
utils::{
binary_search_map::BinarySearchMap, cell_ext::CellExt, errorfmt::ErrorFmt, rc_eq::rc_eq,
},
video::drm::{
Change, ConnectorStatus, DRM_MODE_ATOMIC_ALLOW_MODESET, DrmBlob, DrmConnector, DrmCrtc,
DrmFb, DrmModeInfo, DrmObject, DrmPlane, PropBlob, hdr_output_metadata,
},
},
arrayvec::ArrayVec,
bstr::ByteSlice,
isnt::std_1::collections::IsntHashMapExt,
std::{any::Any, cell::Cell, mem, rc::Rc, slice},
uapi::c,
};
const LEVEL: log::Level = log::Level::Debug;
#[derive(Default, Clone, Debug)]
pub struct DrmPlaneState {
pub fb_id: DrmFb,
pub src_x: u32,
pub src_y: u32,
pub src_w: u32,
pub src_h: u32,
pub assigned_crtc: DrmCrtc,
pub crtc_id: DrmCrtc,
pub crtc_x: i32,
pub crtc_y: i32,
pub crtc_w: i32,
pub crtc_h: i32,
pub buffers: Option<Rc<[RenderBuffer; 2]>>,
}
#[derive(Default, Clone)]
pub struct DrmCrtcState {
pub active: bool,
pub mode: Option<DrmModeInfo>,
pub mode_blob_id: DrmBlob,
pub mode_blob: Option<Rc<PropBlob>>,
pub vrr_enabled: bool,
pub assigned_connector: DrmConnector,
pub gamma_lut: Option<Rc<BackendGammaLut>>,
pub gamma_lut_blob_id: DrmBlob,
pub gamma_lut_blob: Option<Rc<PropBlob>>,
}
#[derive(Default, Clone, Debug)]
pub struct DrmConnectorState {
pub crtc_id: DrmCrtc,
pub color_space: Option<u64>,
pub hdr_metadata: Option<hdr_output_metadata>,
pub hdr_metadata_blob_id: DrmBlob,
pub hdr_metadata_blob: Option<Rc<PropBlob>>,
pub locked: bool,
pub fb: DrmFb,
pub fb_idx: u64,
pub cursor_fb: DrmFb,
pub cursor_fb_idx: u64,
pub cursor_x: i32,
pub cursor_y: i32,
pub out_fd: Option<SyncFile>,
pub src_w: u32,
pub src_h: u32,
pub crtc_x: i32,
pub crtc_y: i32,
pub crtc_w: i32,
pub crtc_h: i32,
}
struct PlaneConfig {
obj: Rc<MetalPlane>,
new: DrmPlaneState,
changed: ArrayVec<Rc<Cell<bool>>, 4>,
}
struct CrtcConfig {
obj: Rc<MetalCrtc>,
new: DrmCrtcState,
changed: ArrayVec<Rc<Cell<bool>>, 2>,
}
struct ConnectorConfig {
obj: Rc<MetalConnector>,
new: DrmConnectorState,
state: BackendConnectorState,
requested: bool,
changed: Rc<Cell<bool>>,
}
const SIZE: usize = 16;
struct TransactionCommon {
dev: Rc<MetalDrmDeviceData>,
planes: BinarySearchMap<DrmPlane, PlaneConfig, SIZE>,
crtcs: BinarySearchMap<DrmCrtc, CrtcConfig, SIZE>,
connectors: BinarySearchMap<DrmConnector, ConnectorConfig, SIZE>,
}
pub struct MetalDeviceTransaction {
common: TransactionCommon,
allow_direct_scanout: bool,
}
pub struct MetalDeviceTransactionWithDrmState {
common: TransactionCommon,
}
pub struct MetalDeviceTransactionWithChange {
common: TransactionCommon,
change: Change,
}
pub struct MetalDeviceAppliedTransaction {
rollback: MetalDeviceTransactionWithDrmState,
}
impl MetalConnector {
pub fn create_transaction(
&self,
) -> Result<MetalDeviceTransaction, BackendConnectorTransactionError> {
let Some(dev) = self.backend.device_holder.drm_devices.get(&self.dev.devnum) else {
return Err(BackendConnectorTransactionError::MissingDrmDevice(
self.kernel_id(),
));
};
Ok(dev.create_transaction())
}
}
impl MetalDrmDeviceData {
pub fn create_transaction(self: &Rc<Self>) -> MetalDeviceTransaction {
let mut tran = MetalDeviceTransaction {
common: TransactionCommon {
dev: self.clone(),
planes: Default::default(),
crtcs: Default::default(),
connectors: Default::default(),
},
allow_direct_scanout: true,
};
for plane in self.dev.planes.values() {
if plane.lease.is_some() {
continue;
}
tran.common.planes.insert(
plane.id,
PlaneConfig {
obj: plane.clone(),
new: plane.drm_state.borrow().clone(),
changed: Default::default(),
},
);
}
for crtc in self.dev.crtcs.values() {
if crtc.lease.is_some() {
continue;
}
tran.common.crtcs.insert(
crtc.id,
CrtcConfig {
obj: crtc.clone(),
new: crtc.drm_state.borrow().clone(),
changed: Default::default(),
},
);
}
for connector in self.connectors.lock().values() {
if connector.lease.is_some() {
continue;
}
let dd = &*connector.display.borrow();
tran.common.connectors.insert(
connector.id,
ConnectorConfig {
obj: connector.clone(),
new: dd.drm_state.clone(),
state: dd.persistent.state.borrow().clone(),
requested: false,
changed: Default::default(),
},
);
}
tran
}
}
const CURSOR_FORMAT: &Format = ARGB8888;
#[derive(Default, Debug)]
struct CrtcPlanes {
primary: DrmPlane,
cursor: DrmPlane,
}
impl MetalDeviceTransaction {
pub fn add(
&mut self,
connector: &Rc<MetalConnector>,
state: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let Some(config) = self.common.connectors.get_mut(&connector.id) else {
if self.common.dev.connectors.contains(&connector.id) {
return Err(BackendConnectorTransactionError::LeasedConnector(
connector.kernel_id(),
));
}
return Err(BackendConnectorTransactionError::UnknownConnector(
connector.kernel_id(),
));
};
config.state = state;
config.requested = true;
Ok(())
}
pub fn disable_direct_scanout(&mut self) {
self.allow_direct_scanout = false;
}
pub fn calculate_drm_state(
mut self,
) -> Result<MetalDeviceTransactionWithDrmState, BackendConnectorTransactionError> {
let mut unused_crtcs = BinarySearchMap::<_, _, SIZE>::new();
let mut unused_planes = BinarySearchMap::<_, _, SIZE>::new();
let mut crtc_planes = BinarySearchMap::<_, _, SIZE>::new();
let mut syncs = vec![];
let slf = &mut self.common;
for (_, crtc) in &mut slf.crtcs {
crtc_planes.insert(crtc.obj.id, CrtcPlanes::default());
unused_crtcs.insert(crtc.obj.id, ());
}
for (_, connector) in &slf.connectors {
unused_crtcs.remove(&connector.new.crtc_id);
if let Some(crtc) = slf.crtcs.get_mut(&connector.new.crtc_id)
&& crtc.changed.is_empty()
{
crtc.changed.push(connector.changed.clone());
}
}
for (_, plane) in &mut slf.planes {
if let Some(crtc) = slf.crtcs.get_mut(&plane.new.assigned_crtc) {
plane.changed.extend(crtc.changed.iter().cloned());
}
if plane.new.crtc_id.is_some() {
plane.new.assigned_crtc = plane.new.crtc_id;
}
macro_rules! discard_plane {
() => {
unused_planes.insert(plane.obj.id, ());
plane.new.crtc_id = DrmCrtc::NONE;
plane.new.assigned_crtc = DrmCrtc::NONE;
};
}
if plane.new.assigned_crtc.is_none() {
discard_plane!();
continue;
}
if unused_crtcs.contains(&plane.new.assigned_crtc) {
discard_plane!();
continue;
}
let Some(crtc_planes) = crtc_planes.get_mut(&plane.new.assigned_crtc) else {
discard_plane!();
continue;
};
let field = match plane.obj.ty {
PlaneType::Overlay => {
discard_plane!();
continue;
}
PlaneType::Primary => &mut crtc_planes.primary,
PlaneType::Cursor => &mut crtc_planes.cursor,
};
if field.is_some() {
discard_plane!();
continue;
}
*field = plane.obj.id;
}
let dev_ctx = slf.dev.dev.ctx.get();
let render_ctx = slf.dev.dev.backend.ctx.get();
let render_ctx = render_ctx.as_ref().unwrap_or(&dev_ctx);
for connector in slf.connectors.values_mut() {
let state = &connector.state;
let dd = &*connector.obj.display.borrow();
if !state.enabled
|| dd.connection != ConnectorStatus::Connected
|| state.non_desktop_override.unwrap_or(dd.non_desktop)
{
if connector.new.crtc_id.is_some() {
unused_crtcs.insert(connector.new.crtc_id, ());
if let Some(crtc) = slf.crtcs.get(&connector.new.crtc_id) {
let planes = crtc_planes.get_mut(&crtc.obj.id).unwrap();
for plane in [&mut planes.primary, &mut planes.cursor] {
if plane.is_some() {
unused_planes.insert(*plane, ());
let plane = slf.planes.get_mut(plane).unwrap();
plane.new.crtc_id = DrmCrtc::NONE;
plane.new.assigned_crtc = DrmCrtc::NONE;
}
}
*planes = CrtcPlanes::default();
}
}
connector.new = DrmConnectorState::default();
continue;
}
if connector.new.crtc_id.is_none() {
let crtc_id = 'crtc_id: {
for (crtc, _) in &dd.crtcs {
if unused_crtcs.contains(crtc) {
break 'crtc_id crtc;
}
}
return Err(BackendConnectorTransactionError::NoCrtcForConnector(
connector.obj.kernel_id(),
));
};
unused_crtcs.remove(crtc_id);
connector.new.crtc_id = *crtc_id;
}
let crtc = slf.crtcs.get_mut(&connector.new.crtc_id).unwrap();
crtc.new.active = state.active;
crtc.new.assigned_connector = connector.obj.id;
crtc.changed.push(connector.changed.clone());
let crtc_planes = crtc_planes.get_mut(&crtc.obj.id).unwrap();
let plane_not_supports_format = |plane: &MetalPlane| {
let format = match plane.ty {
PlaneType::Overlay => unreachable!(),
PlaneType::Primary => state.format,
PlaneType::Cursor => CURSOR_FORMAT,
};
plane.formats.not_contains_key(&format.drm)
};
for plane in [&mut crtc_planes.primary, &mut crtc_planes.cursor] {
macro_rules! discard_plane {
() => {
unused_planes.insert(*plane, ());
*plane = DrmPlane::NONE;
};
}
if plane.is_none() {
discard_plane!();
continue;
}
let plane = slf.planes.get(plane).unwrap();
if plane_not_supports_format(&plane.obj) {
discard_plane!();
continue;
}
}
for (primary, plane) in [
(true, &mut crtc_planes.primary),
(false, &mut crtc_planes.cursor),
] {
if plane.is_some() {
continue;
}
let ty = match primary {
true => PlaneType::Primary,
false => PlaneType::Cursor,
};
for (_, p) in &crtc.obj.possible_planes {
if p.ty != ty {
continue;
}
if unused_planes.not_contains(&p.id) {
continue;
}
if plane_not_supports_format(p) {
continue;
}
*plane = p.id;
unused_planes.remove(&p.id);
break;
}
}
if crtc_planes.primary.is_none() {
return Err(
BackendConnectorTransactionError::NoPrimaryPlaneForConnector(
connector.obj.kernel_id(),
),
);
}
let mode = 'mode: {
let Some(mode) = dd.modes.iter().find(|m| m.to_backend() == state.mode) else {
return Err(BackendConnectorTransactionError::UnsupportedMode(
connector.obj.kernel_id(),
state.mode,
));
};
if let Some(old) = &crtc.new.mode
&& modes_equal(old, mode)
{
break 'mode mode.clone();
}
crtc.new.mode = Some(mode.clone());
let blob = slf
.dev
.dev
.master
.create_blob(&mode.to_raw())
.map_err(BackendConnectorTransactionError::CreateModeBlob)?;
crtc.new.mode_blob_id = blob.id();
crtc.new.mode_blob = Some(Rc::new(blob));
mode.clone()
};
if crtc.new.gamma_lut != state.gamma_lut {
if let Some(gamma_lut) = &state.gamma_lut {
let blob = slf
.dev
.dev
.master
.create_blob(&gamma_lut.gamma_lut as &[_])
.map_err(BackendConnectorTransactionError::CreateGammaLutBlob)?;
crtc.new.gamma_lut_blob_id = blob.id();
crtc.new.gamma_lut_blob = Some(Rc::new(blob));
} else {
crtc.new.gamma_lut_blob_id = DrmBlob::NONE;
crtc.new.gamma_lut_blob = None;
}
crtc.new.gamma_lut = state.gamma_lut.clone();
}
for plane_id in [&mut crtc_planes.primary, &mut crtc_planes.cursor] {
if plane_id.is_none() {
continue;
}
let plane = slf.planes.get_mut(plane_id).unwrap();
plane.new.assigned_crtc = crtc.obj.id;
plane.changed.extend(crtc.changed.iter().cloned());
let (x, y, width, height, format, old_buffers);
match plane.obj.ty {
PlaneType::Overlay => unreachable!(),
PlaneType::Primary => {
(x, y) = (0, 0);
width = mode.hdisplay as i32;
height = mode.vdisplay as i32;
format = state.format;
old_buffers = connector.obj.buffers.get();
}
PlaneType::Cursor => {
x = connector.new.cursor_x;
y = connector.new.cursor_y;
width = connector.obj.dev.cursor_width as i32;
height = connector.obj.dev.cursor_height as i32;
format = CURSOR_FORMAT;
old_buffers = connector.obj.cursor_buffers.get();
}
};
plane.new.buffers = old_buffers.clone();
plane.new.src_x = 0;
plane.new.src_y = 0;
plane.new.src_w = (width as u32) << 16;
plane.new.src_h = (height as u32) << 16;
plane.new.crtc_x = x;
plane.new.crtc_y = y;
plane.new.crtc_w = width;
plane.new.crtc_h = height;
if let Some(b) = &plane.new.buffers {
'discard: {
macro_rules! discard {
() => {
plane.new.buffers = None;
break 'discard;
};
}
if b[0].width != width || b[0].height != height || b[0].format != format {
discard!();
}
if !rc_eq(render_ctx, &b[0].render.ctx) {
discard!();
}
if !rc_eq(&dev_ctx, &b[0].dev_ctx) {
discard!();
}
let modifiers = &plane.obj.formats.get(&format.drm).unwrap().modifiers;
for b in &**b {
if !modifiers.contains(&b.dev_bo().dmabuf().modifier) {
discard!();
}
}
}
}
let mut new_buffers = None;
let current_buffers = match &plane.new.buffers {
Some(b) => b.clone(),
None => {
let modifiers = &plane.obj.formats.get(&format.drm).unwrap().modifiers;
connector.changed.set(true);
let is_cursor = plane.obj.ty == PlaneType::Cursor;
let res = slf
.dev
.dev
.backend
.create_scanout_buffers(
&slf.dev.dev,
format,
modifiers,
width,
height,
render_ctx,
is_cursor,
)
.map_err(|e| {
BackendConnectorTransactionError::AllocateScanoutBuffers(
connector.obj.kernel_id(),
Box::new(e),
)
});
if let Err(e) = &res
&& is_cursor
{
log::error!(
"Could not allocate buffers for cursor plane of {}: {}",
connector.obj.kernel_id(),
ErrorFmt(e),
);
plane.new = DrmPlaneState::default();
unused_planes.insert(*plane_id, ());
*plane_id = DrmPlane::NONE;
continue;
}
let res = res?;
if let Some(method) = res[0].prime.method() {
let plane = match plane.obj.ty {
PlaneType::Overlay => "overlay",
PlaneType::Primary => "primary",
PlaneType::Cursor => "cursor",
};
let dev = slf.dev.dev.devnode.as_bytes().as_bstr();
let connector = connector.obj.kernel_id();
log::info!(
"Using prime method {method} for {dev} {connector} ({plane})",
);
}
let buffers = Rc::new(res);
plane.new.buffers = Some(buffers.clone());
new_buffers = Some(buffers.clone());
buffers
}
};
let (fb_id, fb_idx) = match plane.obj.ty {
PlaneType::Overlay => unreachable!(),
PlaneType::Primary => (connector.new.fb, &mut connector.new.fb_idx),
PlaneType::Cursor => {
(connector.new.cursor_fb, &mut connector.new.cursor_fb_idx)
}
};
plane.new.crtc_id = DrmCrtc::NONE;
plane.new.fb_id = DrmFb::NONE;
if plane.obj.ty == PlaneType::Primary || fb_id.is_some() {
plane.new.crtc_id = crtc.obj.id;
let locked = slf.dev.dev.backend.state.lock.locked.get();
let may_show_current_fb = !crtc.new.active
|| connector.new.locked
|| !locked
|| plane.obj.ty != PlaneType::Primary;
if plane.obj.ty == PlaneType::Primary
&& connector.obj.direct_scanout_active.get()
&& self.allow_direct_scanout
&& may_show_current_fb
{
plane.new.fb_id = fb_id;
macro_rules! copy {
($field:ident) => {
plane.new.$field = connector.new.$field;
};
}
copy!(src_w);
copy!(src_h);
copy!(crtc_x);
copy!(crtc_y);
copy!(crtc_w);
copy!(crtc_h);
} else if current_buffers.iter().any(|b| b.drm.id() == fb_id)
&& may_show_current_fb
{
plane.new.fb_id = fb_id;
} else if let Some(new_buffers) = &new_buffers {
let new_buffer = &new_buffers[0];
plane.new.fb_id = new_buffer.drm.id();
*fb_idx = 0;
let cd = connector.obj.color_description.get();
let res = if let Some(prev) = &old_buffers
&& let Some(prev) = prev.iter().find(|b| b.drm.id() == fb_id)
&& may_show_current_fb
{
match prev.copy_to_new(new_buffer, &cd) {
Ok(sf) => Ok(sf),
Err(e) => {
log::warn!("Could not copy from old buffer: {}", ErrorFmt(e));
new_buffer.clear(&cd)
}
}
} else {
new_buffer.clear(&cd)
};
match res {
Ok(sf) => syncs.extend(sf),
Err(e) => {
log::warn!("Could not clear new buffer: {}", ErrorFmt(e));
}
}
} else {
if may_show_current_fb {
let idx = *fb_idx % current_buffers.len() as u64;
plane.new.fb_id = current_buffers[idx as usize].drm.id();
} else {
let idx = (*fb_idx + 1) % current_buffers.len() as u64;
*fb_idx = idx;
let buffer = &current_buffers[idx as usize];
plane.new.fb_id = buffer.drm.id();
if !buffer.locked.get() {
if !connector.obj.buffers_idle.get()
&& let Some(fd) = &connector.new.out_fd
{
log::log!(LEVEL, "waiting for CRTC sync file before blanking");
let mut pollfd = c::pollfd {
fd: fd.raw(),
events: c::POLLIN,
revents: 0,
};
let res = uapi::poll(slice::from_mut(&mut pollfd), -1);
if let Err(e) = res {
log::warn!(
"Could not wait for CRTC sync file to become readable: {}",
ErrorFmt(e),
);
}
}
buffer.damage_full();
let cd = connector.obj.color_description.get();
let res = buffer.clear(&cd);
match res {
Ok(sf) => {
buffer.locked.set(true);
syncs.extend(sf);
}
Err(e) => {
log::error!(
"Could not black out old buffer: {}",
ErrorFmt(e),
);
}
}
}
}
}
}
if plane.obj.ty == PlaneType::Primary {
macro_rules! copy {
($field:ident) => {
connector.new.$field = plane.new.$field;
};
}
copy!(src_w);
copy!(src_h);
copy!(crtc_x);
copy!(crtc_y);
copy!(crtc_w);
copy!(crtc_h);
}
}
if state.vrr && !dd.vrr_capable {
return Err(BackendConnectorTransactionError::NotVrrCapable(
connector.obj.kernel_id(),
));
}
crtc.new.vrr_enabled = state.vrr;
if state.tearing && !slf.dev.dev.supports_async_commit {
return Err(BackendConnectorTransactionError::TearingNotSupported(
connector.obj.kernel_id(),
));
}
match state.color_space {
BackendColorSpace::Default => {}
BackendColorSpace::Bt2020 => {
if !dd.supports_bt2020 {
return Err(BackendConnectorTransactionError::ColorSpaceNotSupported(
connector.obj.kernel_id(),
state.color_space,
));
}
}
}
match state.eotf {
BackendEotfs::Default => {}
BackendEotfs::Pq => {
if !dd.supports_pq {
return Err(BackendConnectorTransactionError::EotfNotSupported(
connector.obj.kernel_id(),
state.eotf,
));
}
}
}
if let Some(cs) = &mut connector.new.color_space {
*cs = state.color_space.to_drm();
}
if dd.hdr_metadata.is_some() {
let new = if state.eotf == BackendEotfs::Default {
None
} else {
Some(hdr_output_metadata::from_eotf(state.eotf.to_drm()))
};
if connector.new.hdr_metadata != new {
if let Some(new) = &new {
let blob = slf
.dev
.dev
.master
.create_blob(new)
.map_err(BackendConnectorTransactionError::CreateHdrMetadataBlob)?;
connector.new.hdr_metadata_blob_id = blob.id();
connector.new.hdr_metadata_blob = Some(Rc::new(blob));
} else {
connector.new.hdr_metadata_blob_id = DrmBlob::NONE;
connector.new.hdr_metadata_blob = None;
}
connector.new.hdr_metadata = new;
} else if new.is_none() {
connector.new.hdr_metadata_blob_id = DrmBlob::NONE;
connector.new.hdr_metadata_blob = None;
}
}
}
for (crtc, _) in &unused_crtcs {
if let Some(crtc) = slf.crtcs.get_mut(crtc) {
crtc.new = DrmCrtcState::default();
}
}
for (plane, _) in &unused_planes {
if let Some(plane) = slf.planes.get_mut(plane) {
plane.new = DrmPlaneState::default();
}
}
for sync in syncs {
sync.signaled_blocking("transaction");
}
Ok(MetalDeviceTransactionWithDrmState {
common: self.common,
})
}
}
macro_rules! log_change {
($o:expr, $n:expr, $field:ident) => {
log::log!(
LEVEL,
"changed {}: {:?} -> {:?}",
stringify!($field),
$o.$field,
$n.$field
);
};
}
impl MetalDeviceTransactionWithDrmState {
pub fn calculate_change(
mut self,
test: bool,
reset_default_properties: bool,
) -> Result<MetalDeviceTransactionWithChange, BackendConnectorTransactionError> {
macro_rules! reset_default_properties {
($c:expr, $props:expr, $defaults:expr $(,)?) => {{
if reset_default_properties {
let props = $props;
for dp in $defaults {
let old = props.get(&dp.prop).copied().unwrap_or_default();
let new = dp.value;
if old != new {
log::log!(LEVEL, "changed {}: {old} -> {new}", dp.name);
$c.change(dp.prop, new);
}
}
}
}};
}
let slf = &mut self.common;
let mut c = slf.dev.dev.master.change();
for (_, connector) in &mut slf.connectors {
let dd = &*connector.obj.display.borrow();
let n = &mut connector.new;
let o = &dd.drm_state;
let changed = c.change_object(connector.obj.id, |c| {
if n.crtc_id != o.crtc_id {
log_change!(o, n, crtc_id);
c.change(dd.crtc_id, n.crtc_id);
}
if let Some(prop) = &dd.colorspace
&& let Some(new_cs) = n.color_space
&& let Some(old_cs) = o.color_space
&& new_cs != old_cs
{
log_change!(o, n, color_space);
c.change(*prop, new_cs);
}
if let Some(prop) = &dd.hdr_metadata
&& n.hdr_metadata_blob_id != o.hdr_metadata_blob_id
{
log_change!(o, n, hdr_metadata_blob_id);
c.change(*prop, n.hdr_metadata_blob_id);
}
reset_default_properties!(c, &dd.untyped_properties, &dd.default_properties);
});
if changed {
connector.changed.set(true);
}
log::log!(
LEVEL,
"connector {:?} (crtc {:?}) {}changed",
connector.obj.id,
connector.new.crtc_id,
if changed { "" } else { "un" },
);
}
for (_, crtc) in &mut slf.crtcs {
let n = &mut crtc.new;
let o = &*crtc.obj.drm_state.borrow();
let changed = c.change_object(crtc.obj.id, |c| {
if n.active != o.active {
log_change!(o, n, active);
c.change(crtc.obj.active, n.active);
}
if n.vrr_enabled != o.vrr_enabled {
log_change!(o, n, vrr_enabled);
c.change(crtc.obj.vrr_enabled, n.vrr_enabled);
}
if n.mode_blob_id != o.mode_blob_id {
log_change!(o, n, mode_blob_id);
c.change(crtc.obj.mode_id, n.mode_blob_id);
}
if let Some(gamma_lut) = crtc.obj.gamma_lut
&& n.gamma_lut_blob_id != o.gamma_lut_blob_id
{
log_change!(o, n, gamma_lut_blob_id);
c.change(gamma_lut, n.gamma_lut_blob_id);
}
reset_default_properties!(
c,
&*crtc.obj.untyped_properties.borrow(),
&crtc.obj.default_properties,
);
});
if changed {
log::log!(LEVEL, "crtc {:?} changed", crtc.obj.id);
crtc.changed.iter().for_each(|c| c.set(true));
}
}
for (_, plane) in &mut slf.planes {
let n = &mut plane.new;
let o = &*plane.obj.drm_state.borrow();
let changed = c.change_object(plane.obj.id, |c| {
if n.fb_id != o.fb_id {
log_change!(o, n, fb_id);
c.change(plane.obj.fb_id, n.fb_id);
c.change(plane.obj.in_fence_fd, -1i32);
}
if n.crtc_id != o.crtc_id {
log_change!(o, n, crtc_id);
c.change(plane.obj.crtc_id, n.crtc_id);
}
macro_rules! change {
($field:ident) => {
if n.$field != o.$field {
log_change!(o, n, $field);
c.change(plane.obj.$field, n.$field);
}
};
}
change!(src_x);
change!(src_y);
change!(src_w);
change!(src_h);
change!(crtc_x);
change!(crtc_y);
change!(crtc_w);
change!(crtc_h);
reset_default_properties!(
c,
&*plane.obj.untyped_properties.borrow(),
&plane.obj.default_properties,
);
});
if changed {
plane.changed.iter().for_each(|c| c.set(true));
}
log::log!(
LEVEL,
"plane {:?} (crtc {:?}) (ty {:?}) {}changed",
plane.obj.id,
plane.new.crtc_id,
plane.obj.ty,
if changed { "" } else { "un" },
);
}
log::log!(
LEVEL,
"device {} {}changed",
self.common.dev.dev.devnode.to_bytes().as_bstr(),
if c.is_not_empty() { "" } else { "un" },
);
if test {
c.test(DRM_MODE_ATOMIC_ALLOW_MODESET)
.map_err(BackendConnectorTransactionError::AtomicTestFailed)?;
}
Ok(MetalDeviceTransactionWithChange {
common: self.common,
change: c,
})
}
}
impl MetalDeviceTransactionWithChange {
pub fn apply(
mut self,
) -> Result<MetalDeviceAppliedTransaction, BackendConnectorTransactionError> {
let c = &self.change;
if c.is_not_empty()
&& let Err(e) = c.commit(0, 0)
{
log::log!(
LEVEL,
"Transaction of device {} could not be applied without modeset: {}",
self.common.dev.dev.devnode.to_bytes().as_bstr(),
ErrorFmt(e),
);
log::log!(LEVEL, "Performing modeset");
c.commit(DRM_MODE_ATOMIC_ALLOW_MODESET, 0)
.map_err(BackendConnectorTransactionError::AtomicCommitFailed)?;
}
let slf = &mut self.common;
let mut crtc_planes = BinarySearchMap::<_, _, SIZE>::new();
for (_, crtc) in &mut slf.crtcs {
crtc.obj.connector.set(None);
if crtc.new.assigned_connector.is_some() {
let connector = slf
.dev
.connectors
.get(&crtc.new.assigned_connector)
.unwrap();
crtc.obj.connector.set(Some(connector));
crtc_planes.insert(crtc.obj.id, CrtcPlanes::default());
}
}
for (_, plane) in &mut slf.planes {
if plane.new.assigned_crtc.is_some() {
let crtc = slf.crtcs.get(&plane.new.assigned_crtc).unwrap();
let mode = crtc.new.mode.as_ref().unwrap();
plane.obj.mode_w.set(mode.hdisplay as _);
plane.obj.mode_h.set(mode.vdisplay as _);
let planes = crtc_planes.get_mut(&plane.new.assigned_crtc).unwrap();
match plane.obj.ty {
PlaneType::Overlay => unreachable!(),
PlaneType::Primary => planes.primary = plane.obj.id,
PlaneType::Cursor => planes.cursor = plane.obj.id,
}
}
}
for (_, connector) in &mut slf.connectors {
if !connector.changed.get() {
continue;
}
connector.obj.version.fetch_add(1);
if connector.new.crtc_id.is_none() {
connector.obj.crtc.set(None);
connector.obj.primary_plane.set(None);
connector.obj.cursor_plane.set(None);
connector.obj.buffers.set(None);
connector.obj.cursor_buffers.set(None);
} else {
let crtc = slf.crtcs.get(&connector.new.crtc_id).unwrap();
crtc.obj.connector.set(Some(connector.obj.clone()));
connector.obj.crtc.set(Some(crtc.obj.clone()));
connector.obj.crtc_idle.set(crtc.obj.pending_flip.is_none());
let planes = crtc_planes.get(&crtc.obj.id).unwrap();
for (primary, plane) in [(true, planes.primary), (false, planes.cursor)] {
if plane.is_none() {
match primary {
true => {
connector.obj.primary_plane.set(None);
connector.obj.buffers.set(None);
connector.new.fb = DrmFb::NONE;
}
false => {
connector.obj.cursor_plane.set(None);
connector.obj.cursor_buffers.set(None);
connector.new.cursor_fb = DrmFb::NONE;
}
}
continue;
}
let plane = slf.planes.get(&plane).unwrap();
match plane.obj.ty {
PlaneType::Overlay => unreachable!(),
PlaneType::Primary => {
connector.obj.primary_plane.set(Some(plane.obj.clone()));
connector.obj.buffers.set(plane.new.buffers.clone());
connector.new.fb = plane.new.fb_id;
}
PlaneType::Cursor => {
connector.obj.cursor_plane.set(Some(plane.obj.clone()));
connector.obj.cursor_buffers.set(plane.new.buffers.clone());
connector.new.cursor_fb = plane.new.fb_id;
}
}
}
}
}
for (_, crtc) in &mut slf.crtcs {
let o = &mut *crtc.obj.drm_state.borrow_mut();
mem::swap(o, &mut crtc.new);
}
for (_, plane) in &mut slf.planes {
let o = &mut *plane.obj.drm_state.borrow_mut();
mem::swap(o, &mut plane.new);
}
for (_, connector) in &mut slf.connectors {
let is_connected;
let is_non_desktop;
{
let dd = &mut *connector.obj.display.borrow_mut();
mem::swap(&mut dd.drm_state, &mut connector.new);
mem::swap(&mut *dd.persistent.state.borrow_mut(), &mut connector.state);
dd.update_cached_fields(&slf.dev.dev);
is_non_desktop = dd.non_desktop_effective;
is_connected = dd.connection == ConnectorStatus::Connected;
}
if connector.obj.crtc.is_some() {
if connector.changed.get() {
if let Some(buffers) = connector.obj.buffers.get() {
buffers[0].damage_full();
}
connector.obj.has_damage.fetch_add(1);
connector.obj.cursor_damage.set(true);
if connector.obj.buffers_idle.get() && connector.obj.crtc_idle.get() {
connector.obj.schedule_present();
}
}
match connector.obj.frontend_state.get() {
FrontState::Removed | FrontState::Unavailable => {}
FrontState::Disconnected => connector.obj.send_connected(),
FrontState::Connected { non_desktop: false } => {
if connector.changed.get() || connector.requested {
connector.obj.send_hardware_cursor();
connector.obj.send_formats();
connector.obj.update_drm_feedback();
connector.obj.send_state();
}
}
FrontState::Connected { non_desktop: true } => {
connector.obj.send_event(ConnectorEvent::Disconnected);
connector.obj.send_connected();
}
}
} else if is_connected && is_non_desktop {
match connector.obj.frontend_state.get() {
FrontState::Removed
| FrontState::Unavailable
| FrontState::Connected { non_desktop: true } => {}
FrontState::Disconnected => connector.obj.send_connected(),
FrontState::Connected { non_desktop: false } => {
connector.obj.send_event(ConnectorEvent::Disconnected);
connector.obj.send_connected();
}
}
} else {
match connector.obj.frontend_state.get() {
FrontState::Removed | FrontState::Unavailable | FrontState::Disconnected => {}
FrontState::Connected { .. } => {
connector.obj.send_event(ConnectorEvent::Disconnected);
}
}
}
}
Ok(MetalDeviceAppliedTransaction {
rollback: MetalDeviceTransactionWithDrmState {
common: self.common,
},
})
}
}
fn modes_equal(a: &DrmModeInfo, b: &DrmModeInfo) -> bool {
a.clock == b.clock
&& a.hdisplay == b.hdisplay
&& a.hsync_start == b.hsync_start
&& a.hsync_end == b.hsync_end
&& a.htotal == b.htotal
&& a.hskew == b.hskew
&& a.vdisplay == b.vdisplay
&& a.vsync_start == b.vsync_start
&& a.vsync_end == b.vsync_end
&& a.vtotal == b.vtotal
&& a.vscan == b.vscan
&& a.vrefresh == b.vrefresh
&& a.flags == b.flags
}
impl MetalDeviceAppliedTransaction {
pub fn rollback(self) -> Result<(), BackendConnectorTransactionError> {
self.rollback.calculate_change(false, false)?.apply()?;
Ok(())
}
}
impl BackendConnectorTransaction for MetalDeviceTransaction {
fn add(
&mut self,
connector: &Rc<dyn Connector>,
change: BackendConnectorState,
) -> Result<(), BackendConnectorTransactionError> {
let Ok(connector) = (connector.clone() as Rc<dyn Any>).downcast::<MetalConnector>() else {
return Err(BackendConnectorTransactionError::UnsupportedConnectorType(
connector.kernel_id(),
));
};
self.add(&connector, change)?;
Ok(())
}
fn prepare(
self: Box<Self>,
) -> Result<Box<dyn BackendPreparedConnectorTransaction>, BackendConnectorTransactionError>
{
self.calculate_drm_state()?
.calculate_change(true, false)
.map(|v| Box::new(v) as _)
}
}
impl BackendPreparedConnectorTransaction for MetalDeviceTransactionWithChange {
fn apply(
self: Box<Self>,
) -> Result<Box<dyn BackendAppliedConnectorTransaction>, BackendConnectorTransactionError> {
(*self).apply().map(|v| Box::new(v) as _)
}
}
impl BackendAppliedConnectorTransaction for MetalDeviceAppliedTransaction {
fn commit(self: Box<Self>) {
// nothing
}
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
(*self).rollback()
}
}