docs: Add more explanations to the atomic-drm-code
This commit is contained in:
parent
7e8f6b2955
commit
dcb3bb79a7
|
@ -2,6 +2,14 @@
|
||||||
//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface)
|
//! [`RawDevice`](RawDevice) and [`RawSurface`](RawSurface)
|
||||||
//! implementations using the atomic mode-setting infrastructure.
|
//! implementations using the atomic mode-setting infrastructure.
|
||||||
//!
|
//!
|
||||||
|
//! Atomic mode-setting (previously referred to a nuclear page-flip) is a new api of the Direct Rendering
|
||||||
|
//! Manager subsystem of the linux kernel. Adaptations of this api can be found in BSD kernels.
|
||||||
|
//!
|
||||||
|
//! This api is objectively better than the outdated legacy-api, but not supported by every driver.
|
||||||
|
//! Initialization will fail, if the api is unsupported. The legacy-api is also wrapped by smithay
|
||||||
|
//! and may be used instead in these cases. Currently there are no features in smithay that are
|
||||||
|
//! exclusive to the atomic api.
|
||||||
|
//!
|
||||||
//! Usually this implementation will wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice).
|
//! Usually this implementation will wrapped into a [`GbmDevice`](::backend::drm::gbm::GbmDevice).
|
||||||
//! Take a look at `anvil`s source code for an example of this.
|
//! Take a look at `anvil`s source code for an example of this.
|
||||||
//!
|
//!
|
||||||
|
@ -88,9 +96,11 @@ impl<A: AsRawFd + 'static> Drop for Dev<A> {
|
||||||
// Here we restore the card/tty's to it's previous state.
|
// Here we restore the card/tty's to it's previous state.
|
||||||
// In case e.g. getty was running on the tty sets the correct framebuffer again,
|
// In case e.g. getty was running on the tty sets the correct framebuffer again,
|
||||||
// so that getty will be visible.
|
// so that getty will be visible.
|
||||||
|
// We do exit correctly if this fails, but the user will be presented with
|
||||||
|
// a black screen if no display handler takes control again.
|
||||||
|
|
||||||
|
// create an atomic mode request consisting of all properties we captured on creation.
|
||||||
let mut req = AtomicModeReq::new();
|
let mut req = AtomicModeReq::new();
|
||||||
|
|
||||||
fn add_multiple_props<T: ResourceHandle>(
|
fn add_multiple_props<T: ResourceHandle>(
|
||||||
req: &mut AtomicModeReq,
|
req: &mut AtomicModeReq,
|
||||||
old_state: &[(T, PropertyValueSet)],
|
old_state: &[(T, PropertyValueSet)],
|
||||||
|
@ -215,6 +225,8 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
|
||||||
|
|
||||||
let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
|
let dev_id = fstat(fd.as_raw_fd()).map_err(Error::UnableToGetDeviceId)?.st_rdev;
|
||||||
|
|
||||||
|
// we wrap some of the internal states in another struct to share with
|
||||||
|
// the surfaces and event loop handlers.
|
||||||
let active = Arc::new(AtomicBool::new(true));
|
let active = Arc::new(AtomicBool::new(true));
|
||||||
let mut dev = Dev {
|
let mut dev = Dev {
|
||||||
fd,
|
fd,
|
||||||
|
@ -225,13 +237,22 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
|
||||||
logger: log.clone(),
|
logger: log.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// we want to modeset, so we better be the master, if we run via a tty session
|
// we need to be the master to do modesetting if we run via a tty session.
|
||||||
|
// This is only needed on older kernels. Newer kernels grant this permission,
|
||||||
|
// if no other process is already the *master*. So we skip over this error.
|
||||||
if dev.acquire_master_lock().is_err() {
|
if dev.acquire_master_lock().is_err() {
|
||||||
warn!(log, "Unable to become drm master, assuming unprivileged mode");
|
warn!(log, "Unable to become drm master, assuming unprivileged mode");
|
||||||
dev.privileged = false;
|
dev.privileged = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// enable the features we need
|
// Enable the features we need.
|
||||||
|
// We technically could use the atomic api without universal plane support.
|
||||||
|
// But the two are almost exclusively implemented together, as plane synchronization
|
||||||
|
// is one of the killer-features of the atomic api.
|
||||||
|
//
|
||||||
|
// We could bould more abstractions in smithay for devices with partial support,
|
||||||
|
// but for now we role with the oldest possible api (legacy) and the newest feature set
|
||||||
|
// we can use (atomic + universal planes), although we barely use planes yet.
|
||||||
dev.set_client_capability(ClientCapability::UniversalPlanes, true)
|
dev.set_client_capability(ClientCapability::UniversalPlanes, true)
|
||||||
.compat()
|
.compat()
|
||||||
.map_err(|source| Error::Access {
|
.map_err(|source| Error::Access {
|
||||||
|
@ -247,7 +268,7 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
|
||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// enumerate (and save) the current device state
|
// Enumerate (and save) the current device state.
|
||||||
let res_handles = ControlDevice::resource_handles(&dev)
|
let res_handles = ControlDevice::resource_handles(&dev)
|
||||||
.compat()
|
.compat()
|
||||||
.map_err(|source| Error::Access {
|
.map_err(|source| Error::Access {
|
||||||
|
@ -266,11 +287,16 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
|
||||||
let mut old_state = dev.old_state.clone();
|
let mut old_state = dev.old_state.clone();
|
||||||
let mut mapping = dev.prop_mapping.clone();
|
let mut mapping = dev.prop_mapping.clone();
|
||||||
|
|
||||||
|
// This helper function takes a snapshot of the current device properties.
|
||||||
|
// (everything in the atomic api is set via properties.)
|
||||||
dev.add_props(res_handles.connectors(), &mut old_state.0)?;
|
dev.add_props(res_handles.connectors(), &mut old_state.0)?;
|
||||||
dev.add_props(res_handles.crtcs(), &mut old_state.1)?;
|
dev.add_props(res_handles.crtcs(), &mut old_state.1)?;
|
||||||
dev.add_props(res_handles.framebuffers(), &mut old_state.2)?;
|
dev.add_props(res_handles.framebuffers(), &mut old_state.2)?;
|
||||||
dev.add_props(planes, &mut old_state.3)?;
|
dev.add_props(planes, &mut old_state.3)?;
|
||||||
|
|
||||||
|
// And because the mapping is not consistent across devices,
|
||||||
|
// we also need to lookup the handle for a property name.
|
||||||
|
// And we do this a fair bit, so lets cache that mapping.
|
||||||
dev.map_props(res_handles.connectors(), &mut mapping.0)?;
|
dev.map_props(res_handles.connectors(), &mut mapping.0)?;
|
||||||
dev.map_props(res_handles.crtcs(), &mut mapping.1)?;
|
dev.map_props(res_handles.crtcs(), &mut mapping.1)?;
|
||||||
dev.map_props(res_handles.framebuffers(), &mut mapping.2)?;
|
dev.map_props(res_handles.framebuffers(), &mut mapping.2)?;
|
||||||
|
@ -280,6 +306,17 @@ impl<A: AsRawFd + 'static> AtomicDrmDevice<A> {
|
||||||
dev.prop_mapping = mapping;
|
dev.prop_mapping = mapping;
|
||||||
trace!(log, "Mapping: {:#?}", dev.prop_mapping);
|
trace!(log, "Mapping: {:#?}", dev.prop_mapping);
|
||||||
|
|
||||||
|
// If the user does not explicitly requests us to skip this,
|
||||||
|
// we clear out the complete connector<->crtc mapping on device creation.
|
||||||
|
//
|
||||||
|
// The reason is, that certain operations may be racy otherwise. Surfaces can
|
||||||
|
// exist on different threads: as a result, we cannot really enumerate the current state
|
||||||
|
// (it might be changed on another thread during the enumeration). And commits can fail,
|
||||||
|
// if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime.
|
||||||
|
//
|
||||||
|
// An easy workaround is to set a known state on device creation, so we can only
|
||||||
|
// run into these errors on our own and not because previous compositors left the device
|
||||||
|
// in a funny state.
|
||||||
if disable_connectors {
|
if disable_connectors {
|
||||||
// Disable all connectors as initial state
|
// Disable all connectors as initial state
|
||||||
let mut req = AtomicModeReq::new();
|
let mut req = AtomicModeReq::new();
|
||||||
|
|
|
@ -75,6 +75,8 @@ impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
|
||||||
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
|
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
|
||||||
// other ttys that use no cursor, might not clear it themselves.
|
// other ttys that use no cursor, might not clear it themselves.
|
||||||
// This makes sure our cursor won't stay visible.
|
// This makes sure our cursor won't stay visible.
|
||||||
|
//
|
||||||
|
// This usually happens with getty, and a cursor on top of a kernel console looks very weird.
|
||||||
if let Err(err) = surface.clear_plane(surface.planes.cursor) {
|
if let Err(err) = surface.clear_plane(surface.planes.cursor) {
|
||||||
warn!(
|
warn!(
|
||||||
self.logger,
|
self.logger,
|
||||||
|
@ -123,6 +125,13 @@ impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_state(&mut self) -> Result<(), Error> {
|
fn reset_state(&mut self) -> Result<(), Error> {
|
||||||
|
// reset state sets the connectors into a known state (all disabled),
|
||||||
|
// for the same reasons we do this on device creation.
|
||||||
|
//
|
||||||
|
// We might end up with conflicting commit requirements, if we want to restore our state,
|
||||||
|
// on top of the state the previous compositor left the device in.
|
||||||
|
// This is because we do commits per surface and not per device, so we do a global
|
||||||
|
// commit here, to fix any conflicts.
|
||||||
if let Some(dev) = self.dev.upgrade() {
|
if let Some(dev) = self.dev.upgrade() {
|
||||||
let res_handles = ControlDevice::resource_handles(&*dev)
|
let res_handles = ControlDevice::resource_handles(&*dev)
|
||||||
.compat()
|
.compat()
|
||||||
|
@ -172,6 +181,11 @@ impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
|
||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// because we change the state and disabled everything,
|
||||||
|
// we want to force a commit (instead of a page-flip) on all used surfaces
|
||||||
|
// for the next rendering step to re-activate the connectors.
|
||||||
|
//
|
||||||
|
// Lets do that, by creating a garbage/non-matching current-state.
|
||||||
if let Some(backends) = self.backends.upgrade() {
|
if let Some(backends) = self.backends.upgrade() {
|
||||||
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
|
for surface in backends.borrow().values().filter_map(Weak::upgrade) {
|
||||||
let mut current = surface.state.write().unwrap();
|
let mut current = surface.state.write().unwrap();
|
||||||
|
@ -187,7 +201,7 @@ impl<A: AsRawFd + 'static> AtomicDrmDeviceObserver<A> {
|
||||||
};
|
};
|
||||||
surface.use_mode(mode)?;
|
surface.use_mode(mode)?;
|
||||||
|
|
||||||
// drop cursor state
|
// drop cursor state to force setting the cursor again.
|
||||||
surface.cursor.position.set(None);
|
surface.cursor.position.set(None);
|
||||||
surface.cursor.hotspot.set((0, 0));
|
surface.cursor.hotspot.set((0, 0));
|
||||||
surface.cursor.framebuffer.set(None);
|
surface.cursor.framebuffer.set(None);
|
||||||
|
|
|
@ -111,6 +111,10 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
source,
|
source,
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
|
// the current set of connectors are those, that already have the correct `CRTC_ID` set.
|
||||||
|
// so we collect them for `current_state` and set the user-given once in `pending_state`.
|
||||||
|
//
|
||||||
|
// If they don't match, `commit_pending` will return true and they will be changed on the next `commit`.
|
||||||
let mut current_connectors = HashSet::new();
|
let mut current_connectors = HashSet::new();
|
||||||
for conn in res_handles.connectors() {
|
for conn in res_handles.connectors() {
|
||||||
let crtc_prop = dev
|
let crtc_prop = dev
|
||||||
|
@ -152,6 +156,9 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
connectors: connectors.iter().copied().collect(),
|
connectors: connectors.iter().copied().collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// we need to find planes for this crtc.
|
||||||
|
// (cursor and primary planes are usually available once for every crtc,
|
||||||
|
// so this is a very naive algorithm.)
|
||||||
let (primary, cursor) =
|
let (primary, cursor) =
|
||||||
AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes {
|
AtomicDrmSurfaceInternal::find_planes(&dev, crtc).ok_or(Error::NoSuitablePlanes {
|
||||||
crtc,
|
crtc,
|
||||||
|
@ -175,6 +182,8 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
Ok(surface)
|
Ok(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need a framebuffer to do test commits, which we use to verify our pending state.
|
||||||
|
// here we create a dumbbuffer for that purpose.
|
||||||
fn create_test_buffer(&self, mode: &Mode) -> Result<framebuffer::Handle, Error> {
|
fn create_test_buffer(&self, mode: &Mode) -> Result<framebuffer::Handle, Error> {
|
||||||
let (w, h) = mode.size();
|
let (w, h) = mode.size();
|
||||||
let db = self
|
let db = self
|
||||||
|
@ -367,6 +376,7 @@ impl<A: AsRawFd + 'static> Surface for AtomicDrmSurfaceInternal<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
|
fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
|
||||||
|
// the test would also prevent this, but the error message is far less helpful
|
||||||
if connectors.is_empty() {
|
if connectors.is_empty() {
|
||||||
return Err(Error::SurfaceWithoutConnectors(self.crtc));
|
return Err(Error::SurfaceWithoutConnectors(self.crtc));
|
||||||
}
|
}
|
||||||
|
@ -466,6 +476,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
"Preparing Commit.\n\tCurrent: {:?}\n\tPending: {:?}\n", *current, *pending
|
"Preparing Commit.\n\tCurrent: {:?}\n\tPending: {:?}\n", *current, *pending
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// we need the differences to know, which connectors need to change properties
|
||||||
let current_conns = current.connectors.clone();
|
let current_conns = current.connectors.clone();
|
||||||
let pending_conns = pending.connectors.clone();
|
let pending_conns = pending.connectors.clone();
|
||||||
let mut removed = current_conns.difference(&pending_conns);
|
let mut removed = current_conns.difference(&pending_conns);
|
||||||
|
@ -493,6 +504,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
|
|
||||||
trace!(self.logger, "Testing screen config");
|
trace!(self.logger, "Testing screen config");
|
||||||
|
|
||||||
|
// test the new config and return the request if it would be accepted by the driver.
|
||||||
let req = {
|
let req = {
|
||||||
let req = self.build_request(
|
let req = self.build_request(
|
||||||
&mut added,
|
&mut added,
|
||||||
|
@ -533,8 +545,11 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
let result = self
|
let result = self
|
||||||
.atomic_commit(
|
.atomic_commit(
|
||||||
&[
|
&[
|
||||||
|
// on the atomic api we can modeset and trigger a page_flip event on the same call!
|
||||||
AtomicCommitFlags::PageFlipEvent,
|
AtomicCommitFlags::PageFlipEvent,
|
||||||
AtomicCommitFlags::AllowModeset,
|
AtomicCommitFlags::AllowModeset,
|
||||||
|
// we also do not need to wait for completion, like with `set_crtc`.
|
||||||
|
// and have tested this already, so we do not expect any errors later down the line.
|
||||||
AtomicCommitFlags::Nonblock,
|
AtomicCommitFlags::Nonblock,
|
||||||
],
|
],
|
||||||
req,
|
req,
|
||||||
|
@ -558,6 +573,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
return Err(Error::DeviceInactive);
|
return Err(Error::DeviceInactive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// page flips work just like commits with fewer parameters..
|
||||||
let req = self.build_request(
|
let req = self.build_request(
|
||||||
&mut [].iter(),
|
&mut [].iter(),
|
||||||
&mut [].iter(),
|
&mut [].iter(),
|
||||||
|
@ -567,6 +583,9 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
// .. and without `AtomicCommitFlags::AllowModeset`.
|
||||||
|
// If we would set anything here, that would require a modeset, this would fail,
|
||||||
|
// indicating a problem in our assumptions.
|
||||||
trace!(self.logger, "Queueing page flip: {:?}", req);
|
trace!(self.logger, "Queueing page flip: {:?}", req);
|
||||||
self.atomic_commit(
|
self.atomic_commit(
|
||||||
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock],
|
&[AtomicCommitFlags::PageFlipEvent, AtomicCommitFlags::Nonblock],
|
||||||
|
@ -583,6 +602,7 @@ impl<A: AsRawFd + 'static> RawSurface for AtomicDrmSurfaceInternal<A> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this whole implementation just queues the cursor state for the next commit.
|
||||||
impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
|
impl<A: AsRawFd + 'static> CursorBackend for AtomicDrmSurfaceInternal<A> {
|
||||||
type CursorFormat = dyn Buffer;
|
type CursorFormat = dyn Buffer;
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
|
@ -708,8 +728,13 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
mode: Option<Mode>,
|
mode: Option<Mode>,
|
||||||
blob: Option<property::Value<'static>>,
|
blob: Option<property::Value<'static>>,
|
||||||
) -> Result<AtomicModeReq, Error> {
|
) -> Result<AtomicModeReq, Error> {
|
||||||
|
// okay, here we build the actual requests used by the surface.
|
||||||
let mut req = AtomicModeReq::new();
|
let mut req = AtomicModeReq::new();
|
||||||
|
|
||||||
|
// requests consist out of a set of properties and their new values
|
||||||
|
// for different drm objects (crtc, plane, connector, ...).
|
||||||
|
|
||||||
|
// for every connector that is new, we need to set our crtc_id
|
||||||
for conn in new_connectors {
|
for conn in new_connectors {
|
||||||
req.add_property(
|
req.add_property(
|
||||||
*conn,
|
*conn,
|
||||||
|
@ -718,6 +743,10 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for every connector that got removed, we need to set no crtc_id.
|
||||||
|
// (this is a bit problematic, because this means we need to remove, commit, add, commit
|
||||||
|
// in the right order to move a connector to another surface. otherwise we disable the
|
||||||
|
// the connector here again...)
|
||||||
for conn in removed_connectors {
|
for conn in removed_connectors {
|
||||||
req.add_property(
|
req.add_property(
|
||||||
*conn,
|
*conn,
|
||||||
|
@ -726,16 +755,19 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we need to set the new mode, if there is one
|
||||||
if let Some(blob) = blob {
|
if let Some(blob) = blob {
|
||||||
req.add_property(self.crtc, self.crtc_prop_handle(self.crtc, "MODE_ID")?, blob);
|
req.add_property(self.crtc, self.crtc_prop_handle(self.crtc, "MODE_ID")?, blob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we also need to set this crtc active
|
||||||
req.add_property(
|
req.add_property(
|
||||||
self.crtc,
|
self.crtc,
|
||||||
self.crtc_prop_handle(self.crtc, "ACTIVE")?,
|
self.crtc_prop_handle(self.crtc, "ACTIVE")?,
|
||||||
property::Value::Boolean(true),
|
property::Value::Boolean(true),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// and we need to set the framebuffer for our primary plane
|
||||||
if let Some(fb) = framebuffer {
|
if let Some(fb) = framebuffer {
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.primary,
|
planes.primary,
|
||||||
|
@ -744,12 +776,14 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if there is a new mode, we shoudl also make sure the plane is connected
|
||||||
if let Some(mode) = mode {
|
if let Some(mode) = mode {
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.primary,
|
planes.primary,
|
||||||
self.plane_prop_handle(planes.primary, "CRTC_ID")?,
|
self.plane_prop_handle(planes.primary, "CRTC_ID")?,
|
||||||
property::Value::CRTC(Some(self.crtc)),
|
property::Value::CRTC(Some(self.crtc)),
|
||||||
);
|
);
|
||||||
|
// we can take different parts of a plane ...
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.primary,
|
planes.primary,
|
||||||
self.plane_prop_handle(planes.primary, "SRC_X")?,
|
self.plane_prop_handle(planes.primary, "SRC_X")?,
|
||||||
|
@ -763,6 +797,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.primary,
|
planes.primary,
|
||||||
self.plane_prop_handle(planes.primary, "SRC_W")?,
|
self.plane_prop_handle(planes.primary, "SRC_W")?,
|
||||||
|
// these are 16.16. fixed point
|
||||||
property::Value::UnsignedRange((mode.size().0 as u64) << 16),
|
property::Value::UnsignedRange((mode.size().0 as u64) << 16),
|
||||||
);
|
);
|
||||||
req.add_property(
|
req.add_property(
|
||||||
|
@ -770,6 +805,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
self.plane_prop_handle(planes.primary, "SRC_H")?,
|
self.plane_prop_handle(planes.primary, "SRC_H")?,
|
||||||
property::Value::UnsignedRange((mode.size().1 as u64) << 16),
|
property::Value::UnsignedRange((mode.size().1 as u64) << 16),
|
||||||
);
|
);
|
||||||
|
// .. onto different coordinated on the crtc, but we just use a 1:1 mapping.
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.primary,
|
planes.primary,
|
||||||
self.plane_prop_handle(planes.primary, "CRTC_X")?,
|
self.plane_prop_handle(planes.primary, "CRTC_X")?,
|
||||||
|
@ -795,6 +831,8 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
let cursor_pos = self.cursor.position.get();
|
let cursor_pos = self.cursor.position.get();
|
||||||
let cursor_fb = self.cursor.framebuffer.get();
|
let cursor_fb = self.cursor.framebuffer.get();
|
||||||
|
|
||||||
|
// if there is a cursor, we add the cursor plane to the request as well.
|
||||||
|
// this synchronizes cursor movement with rendering, which reduces flickering.
|
||||||
if let (Some(pos), Some(fb)) = (cursor_pos, cursor_fb) {
|
if let (Some(pos), Some(fb)) = (cursor_pos, cursor_fb) {
|
||||||
match self.get_framebuffer(fb).compat().map_err(|source| Error::Access {
|
match self.get_framebuffer(fb).compat().map_err(|source| Error::Access {
|
||||||
errmsg: "Error getting cursor fb",
|
errmsg: "Error getting cursor fb",
|
||||||
|
@ -804,11 +842,13 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
Ok(cursor_info) => {
|
Ok(cursor_info) => {
|
||||||
let hotspot = self.cursor.hotspot.get();
|
let hotspot = self.cursor.hotspot.get();
|
||||||
|
|
||||||
|
// again like the primary plane we need to set crtc and framebuffer.
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.cursor,
|
planes.cursor,
|
||||||
self.plane_prop_handle(planes.cursor, "CRTC_ID")?,
|
self.plane_prop_handle(planes.cursor, "CRTC_ID")?,
|
||||||
property::Value::CRTC(Some(self.crtc)),
|
property::Value::CRTC(Some(self.crtc)),
|
||||||
);
|
);
|
||||||
|
// copy the whole plane
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.cursor,
|
planes.cursor,
|
||||||
self.plane_prop_handle(planes.cursor, "SRC_X")?,
|
self.plane_prop_handle(planes.cursor, "SRC_X")?,
|
||||||
|
@ -829,6 +869,7 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
self.plane_prop_handle(planes.cursor, "SRC_H")?,
|
self.plane_prop_handle(planes.cursor, "SRC_H")?,
|
||||||
property::Value::UnsignedRange((cursor_info.size().1 as u64) << 16),
|
property::Value::UnsignedRange((cursor_info.size().1 as u64) << 16),
|
||||||
);
|
);
|
||||||
|
// but this time add this at some very specific coordinates of the crtc
|
||||||
req.add_property(
|
req.add_property(
|
||||||
planes.cursor,
|
planes.cursor,
|
||||||
self.plane_prop_handle(planes.cursor, "CRTC_X")?,
|
self.plane_prop_handle(planes.cursor, "CRTC_X")?,
|
||||||
|
@ -865,6 +906,9 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
Ok(req)
|
Ok(req)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// primary and cursor planes are almost always unique to a crtc.
|
||||||
|
// otherwise we would be in trouble and would need to figure this out
|
||||||
|
// on the device level to find the best plane combination.
|
||||||
fn find_planes(card: &Dev<A>, crtc: crtc::Handle) -> Option<(plane::Handle, plane::Handle)> {
|
fn find_planes(card: &Dev<A>, crtc: crtc::Handle) -> Option<(plane::Handle, plane::Handle)> {
|
||||||
let res = card.resource_handles().expect("Could not list resources");
|
let res = card.resource_handles().expect("Could not list resources");
|
||||||
let planes = card.plane_handles().expect("Could not list planes");
|
let planes = card.plane_handles().expect("Could not list planes");
|
||||||
|
@ -918,6 +962,10 @@ impl<A: AsRawFd + 'static> AtomicDrmSurfaceInternal<A> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this helper function clears the contents of a single plane.
|
||||||
|
// this is mostly used to remove the cursor, e.g. on tty switch,
|
||||||
|
// as other compositors might not make use of other planes,
|
||||||
|
// leaving our cursor as a relict of a better time on the screen.
|
||||||
pub(super) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> {
|
pub(super) fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> {
|
||||||
let mut req = AtomicModeReq::new();
|
let mut req = AtomicModeReq::new();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue