1168 lines
46 KiB
Rust
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 = ¤t_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()
|
|
}
|
|
}
|