use crate::allocator::BufferObject;
use crate::backend::BackendColorSpace;
use crate::backend::BackendConnectorState;
use crate::backend::BackendEotfs;
use crate::backend::Connector;
use crate::backend::ConnectorEvent;
use crate::backend::ScanoutFormats;
use crate::backend::transaction::BackendAppliedConnectorTransaction;
use crate::backend::transaction::BackendConnectorTransaction;
use crate::backend::transaction::BackendConnectorTransactionError;
use crate::backend::transaction::BackendPreparedConnectorTransaction;
use crate::backends::metal::allocator::RenderBuffer;
use crate::backends::metal::video::DefaultProperty;
use crate::backends::metal::video::FrontState;
use crate::backends::metal::video::MetalConnector;
use crate::backends::metal::video::MetalCrtc;
use crate::backends::metal::video::MetalDrmDeviceData;
use crate::backends::metal::video::MetalPlane;
use crate::backends::metal::video::PlaneDefaultPropertyFilter;
use crate::backends::metal::video::PlaneType;
use crate::backends::metal::video::metal_cm::MetalCmProgramming;
use crate::format::ARGB8888;
use crate::format::Format;
use crate::gfx_api::SyncFile;
use crate::tree::TreeTimeline::RenderTL;
use crate::utils::bhash::BHashMap;
use crate::utils::binary_search_map::BinarySearchMap;
use crate::utils::cell_ext::CellExt;
use crate::utils::errorfmt::ErrorFmt;
use crate::utils::hash_map_ext::HashMapExt;
use crate::utils::obj_and_id::ObjWithId;
use crate::utils::rc_eq::rc_eq;
use crate::utils::reset::Reset;
use crate::video::drm::Change;
use crate::video::drm::ConnectorStatus;
use crate::video::drm::DRM_LINK_STATUS_GOOD;
use crate::video::drm::DRM_MODE_ATOMIC_ALLOW_MODESET;
use crate::video::drm::DrmBlob;
use crate::video::drm::DrmConnector;
use crate::video::drm::DrmCrtc;
use crate::video::drm::DrmFb;
use crate::video::drm::DrmModeInfo;
use crate::video::drm::DrmObject;
use crate::video::drm::DrmPlane;
use crate::video::drm::DrmProperty;
use crate::video::drm::DrmPropertyValue;
use crate::video::drm::Logging;
use crate::video::drm::PrepareDrmObjectProperties;
use crate::video::drm::PropBlob;
use crate::video::drm::hdr_output_metadata;
use arrayvec::ArrayVec;
use bstr::ByteSlice;
use jay_proc::PrepareDrmObjectProperties;
use jay_proc::Reset;
use std::any::Any;
use std::cell::Cell;
use std::mem;
use std::ops::Deref;
use std::ops::DerefMut;
use std::rc::Rc;
use std::slice;
use uapi::c;
const LEVEL: log::Level = log::Level::Debug;
const LOGGING: Option<&Logging> = Some(&Logging {
target: module_path!(),
level: LEVEL,
});
#[derive(Clone, Debug, Reset)]
pub struct DrmPlaneState {
pub assigned_crtc: DrmCrtc,
pub buffers: Option<Rc<[RenderBuffer; 2]>>,
pub props: DrmPlaneStateProps,
}
#[derive(Clone, Debug, PrepareDrmObjectProperties)]
pub struct DrmPlaneStateProps {
pub fb_id: DrmPropertyValue<DrmFb>,
pub src_x: DrmPropertyValue<u32>,
pub src_y: DrmPropertyValue<u32>,
pub src_w: DrmPropertyValue<u32>,
pub src_h: DrmPropertyValue<u32>,
pub crtc_id: DrmPropertyValue<DrmCrtc>,
pub crtc_x: DrmPropertyValue<i32>,
pub crtc_y: DrmPropertyValue<i32>,
pub crtc_w: DrmPropertyValue<i32>,
pub crtc_h: DrmPropertyValue<i32>,
}
#[derive(Clone, Reset)]
pub struct DrmCrtcState {
pub mode: Option<DrmModeInfo>,
pub mode_blob: Option<Rc<PropBlob>>,
pub assigned_connector: DrmConnector,
pub props: DrmCrtcStateProps,
}
#[derive(Clone, Debug, PrepareDrmObjectProperties)]
pub struct DrmCrtcStateProps {
pub active: DrmPropertyValue<bool>,
pub mode_blob_id: DrmPropertyValue<DrmBlob>,
pub vrr_enabled: DrmPropertyValue<bool>,
}
#[derive(Clone, Debug, Reset)]
pub struct DrmConnectorState {
pub hdr_metadata: Option<hdr_output_metadata>,
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,
pub props: DrmConnectorStateProps,
}
#[derive(Clone, Debug, PrepareDrmObjectProperties)]
pub struct DrmConnectorStateProps {
pub link_status: DrmPropertyValue,
pub crtc_id: DrmPropertyValue<DrmCrtc>,
pub color_space: Option<DrmPropertyValue>,
pub hdr_metadata_blob_id: Option<DrmPropertyValue<DrmBlob>>,
}
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>>,
old_scanout_formats: Option<ScanoutFormats>,
old_cm_programming: Option<MetalCmProgramming>,
new_cm_programming: Option<MetalCmProgramming>,
}
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,
}
macro_rules! impl_props_deref {
($outer:ident, $inner:ident) => {
impl Deref for $outer {
type Target = $inner;
fn deref(&self) -> &Self::Target {
&self.props
}
}
impl DerefMut for $outer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.props
}
}
};
}
impl_props_deref!(DrmPlaneState, DrmPlaneStateProps);
impl_props_deref!(DrmCrtcState, DrmCrtcStateProps);
impl_props_deref!(DrmConnectorState, DrmConnectorStateProps);
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(),
old_scanout_formats: connector.scanout_formats(),
old_cm_programming: connector.cm.current(),
new_cm_programming: None,
},
);
}
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.value);
if let Some(crtc) = slf.crtcs.get_mut(&connector.new.crtc_id.value)
&& 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.value.is_some() {
plane.new.assigned_crtc = plane.new.crtc_id.value;
}
macro_rules! discard_plane {
() => {
unused_planes.insert(plane.obj.id, ());
plane.new.crtc_id.value = 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.value.is_some() {
unused_crtcs.insert(connector.new.crtc_id.value, ());
if let Some(crtc) = slf.crtcs.get(&connector.new.crtc_id.value) {
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.value = DrmCrtc::NONE;
plane.new.assigned_crtc = DrmCrtc::NONE;
}
}
*planes = CrtcPlanes::default();
}
}
connector.new.reset();
continue;
}
connector.new.link_status.value = DRM_LINK_STATUS_GOOD;
if connector.new.crtc_id.value.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.value = *crtc_id;
}
let crtc = slf.crtcs.get_mut(&connector.new.crtc_id.value).unwrap();
crtc.new.active.value = 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.value = blob.id();
crtc.new.mode_blob = Some(Rc::new(blob));
mode.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.value = 0;
plane.new.src_y.value = 0;
plane.new.src_w.value = (width as u32) << 16;
plane.new.src_h.value = (height as u32) << 16;
plane.new.crtc_x.value = x;
plane.new.crtc_y.value = y;
plane.new.crtc_w.value = width;
plane.new.crtc_h.value = 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.reset();
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.value = DrmCrtc::NONE;
plane.new.fb_id.value = DrmFb::NONE;
if plane.obj.ty == PlaneType::Primary || fb_id.is_some() {
plane.new.crtc_id.value = crtc.obj.id;
let locked = slf.dev.dev.backend.state.lock.locked[RenderTL].get();
let may_show_current_fb = !crtc.new.active.value
|| 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.value = fb_id;
macro_rules! copy {
($field:ident) => {
plane.new.$field.value = 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.value = fb_id;
} else if let Some(new_buffers) = &new_buffers {
let new_buffer = &new_buffers[0];
plane.new.fb_id.value = 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.value = current_buffers[idx as usize].drm.id();
} else {
let idx = (*fb_idx + 1) % current_buffers.len() as u64;
*fb_idx = idx;
let buffer = ¤t_buffers[idx as usize];
plane.new.fb_id.value = 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.value;
};
}
copy!(src_w);
copy!(src_h);
copy!(crtc_x);
copy!(crtc_y);
copy!(crtc_w);
copy!(crtc_h);
let fb_cd = match self.allow_direct_scanout {
true => connector.obj.fb_color_description.get(),
false => connector.obj.color_description.get(),
};
let p = connector
.obj
.cm
.find_programming(
&slf.dev.dev.master,
&plane.obj,
&crtc.obj,
&fb_cd,
&connector.obj.color_description.get(),
connector.obj.fb_render_intent.get(),
state.gamma_lut.as_ref(),
connector.new.cursor_fb.is_some(),
slf.dev.dev.use_plane_color_pipelines.get(),
)
.ok_or(BackendConnectorTransactionError::NoColorManagementProgramming)?;
if connector.obj.cm.is_different(&p) {
connector.changed.set(true);
}
connector.new_cm_programming = Some(p);
}
}
if state.vrr && !dd.vrr_capable {
return Err(BackendConnectorTransactionError::NotVrrCapable(
connector.obj.kernel_id(),
));
}
crtc.new.vrr_enabled.value = 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.value = state.color_space.to_drm();
}
if let Some(prop) = &mut connector.new.props.hdr_metadata_blob_id {
let new = if state.eotf == BackendEotfs::Default {
None
} else {
Some(hdr_output_metadata::from_eotf(
state.eotf.to_drm(),
&dd.primaries,
dd.luminance.as_ref(),
))
};
if connector.new.hdr_metadata != new {
if let Some(new) = &new {
let blob = slf
.dev
.dev
.master
.create_blob(new)
.map_err(BackendConnectorTransactionError::CreateHdrMetadataBlob)?;
prop.value = blob.id();
connector.new.hdr_metadata_blob = Some(Rc::new(blob));
} else {
prop.value = DrmBlob::NONE;
connector.new.hdr_metadata_blob = None;
}
connector.new.hdr_metadata = new;
} else if new.is_none() {
prop.value = DrmBlob::NONE;
connector.new.hdr_metadata_blob = None;
}
}
}
for (crtc, _) in &unused_crtcs {
if let Some(crtc) = slf.crtcs.get_mut(crtc) {
crtc.new.reset();
}
}
for (plane, _) in &unused_planes {
if let Some(plane) = slf.planes.get_mut(plane) {
plane.new.reset();
}
}
for sync in syncs {
sync.signaled_blocking("transaction");
}
Ok(MetalDeviceTransactionWithDrmState {
common: self.common,
})
}
}
impl MetalDeviceTransactionWithDrmState {
pub fn calculate_change(
mut self,
test: bool,
reset_default_properties: bool,
) -> Result<MetalDeviceTransactionWithChange, BackendConnectorTransactionError> {
fn for_each_changed_default_property<T>(
props: &BHashMap<DrmProperty, u64>,
defaults: &[DefaultProperty<T>],
filter: impl Fn(&T) -> bool,
mut f: impl FnMut(u64, &DefaultProperty<T>),
) {
for dp in defaults {
if filter(&dp.t) {
let old = props.get(&dp.prop).copied().unwrap_or_default();
let new = dp.value;
if old != new {
f(old, dp);
}
}
}
}
macro_rules! need_reset_default_properties {
($props:expr, $defaults:expr $(,)?) => {{ need_reset_default_properties!($props, $defaults, |_: &()| true) }};
($props:expr, $defaults:expr, $filter:expr $(,)?) => {{
let mut changed = false;
if reset_default_properties {
for_each_changed_default_property($props, $defaults, $filter, |_, _| {
changed = true
});
}
changed
}};
}
macro_rules! reset_default_properties {
($c:expr, $props:expr, $defaults:expr $(,)?) => {{
reset_default_properties!($c, $props, $defaults, |_: &()| true);
}};
($c:expr, $props:expr, $defaults:expr, $filter:expr $(,)?) => {{
if reset_default_properties {
for_each_changed_default_property($props, $defaults, $filter, |old, dp| {
log::log!(LEVEL, " changing {} from {old} to {}", dp.name, dp.value);
$c.change(dp.prop, dp.value);
});
}
}};
}
let slf = &mut self.common;
let mut c = slf.dev.dev.master.change();
for (_, connector) in &mut slf.connectors {
if let Some(p) = &connector.new_cm_programming {
connector.obj.cm.prepare(p, &mut c, LOGGING);
}
let dd = &*connector.obj.display.borrow();
let n = &connector.new.props;
let o = &dd.drm_state.props;
let untyped_properties = &dd.untyped_properties;
let default_properties = &dd.default_properties;
let changed = n.differs(o)
|| need_reset_default_properties!(untyped_properties, default_properties);
log::log!(
LEVEL,
"connector {:?} (crtc {:?}) {}changed",
connector.obj.id.0,
connector.new.crtc_id.value.0,
if changed { "" } else { "un" },
);
if changed {
c.change_object(connector.obj.id, |c| {
n.prepare_conditional(o, c, LOGGING);
reset_default_properties!(c, untyped_properties, default_properties);
});
connector.changed.set(true);
}
}
for (_, crtc) in &mut slf.crtcs {
let n = &crtc.new.props;
let o = &crtc.obj.drm_state.borrow().props;
let untyped_properties = &*crtc.obj.untyped_properties.borrow();
let default_properties = &crtc.obj.default_properties;
let changed = n.differs(o)
|| need_reset_default_properties!(untyped_properties, default_properties);
log::log!(
LEVEL,
"crtc {:?} {}changed",
crtc.obj.id.0,
if changed { "" } else { "un" },
);
if changed {
c.change_object(crtc.obj.id, |c| {
n.prepare_conditional(o, c, LOGGING);
reset_default_properties!(c, untyped_properties, default_properties);
});
crtc.changed.iter().for_each(|c| c.set(true));
}
}
for (_, plane) in &mut slf.planes {
let n = &plane.new.props;
let o = &plane.obj.drm_state.borrow().props;
let untyped_properties = &*plane.obj.untyped_properties.borrow();
let default_properties = &plane.obj.default_properties;
let filter = |ty: &PlaneDefaultPropertyFilter| ty[plane.obj.ty];
let changed = n.differs(o)
|| need_reset_default_properties!(untyped_properties, default_properties, filter);
log::log!(
LEVEL,
"plane {:?} (crtc {:?}) (ty {:?}) {}changed",
plane.obj.id.0,
plane.new.crtc_id.value.0,
plane.obj.ty,
if changed { "" } else { "un" },
);
if changed {
c.change_object(plane.obj.id, |c| {
if n.fb_id.value != o.fb_id.value {
c.change(plane.obj.in_fence_fd, -1i32);
}
n.prepare_conditional(o, c, LOGGING);
reset_default_properties!(c, untyped_properties, default_properties, filter);
});
plane.changed.iter().for_each(|c| c.set(true));
}
}
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 let Some(p) = &connector.new_cm_programming {
connector.obj.cm.apply(p);
}
if connector.new.crtc_id.value.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.value).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.value;
}
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.value;
}
}
}
}
}
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 {
connector
.obj
.gamma_lut
.set(connector.state.gamma_lut.clone());
mem::swap(
&mut connector.old_cm_programming,
&mut connector.new_cm_programming,
);
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.send_state();
if connector.obj.scanout_formats() != connector.old_scanout_formats {
let state = &connector.obj.state;
state.dmabuf_feedback.update();
state.update_prime_scanout_modifiers_id(connector.obj.connector_id);
}
}
}
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 {
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>) {
}
fn rollback(self: Box<Self>) -> Result<(), BackendConnectorTransactionError> {
(*self).rollback()
}
}