1
0
Fork 0
forked from wry/wry

wayland: implement commit-timing-v1

This commit is contained in:
Julian Orth 2024-09-26 16:18:17 +02:00
parent d45aaffdb3
commit fac5445f2e
19 changed files with 434 additions and 15 deletions

View file

@ -60,6 +60,7 @@ pub struct WlOutputGlobal {
pub pos: Cell<Rect>,
pub output_id: Rc<OutputId>,
pub mode: Cell<backend::Mode>,
pub refresh_nsec: Cell<u64>,
pub modes: Vec<backend::Mode>,
pub formats: CloneCell<Rc<Vec<&'static Format>>>,
pub format: Cell<&'static Format>,
@ -157,6 +158,7 @@ impl WlOutputGlobal {
pos: Cell::new(Rect::new_sized(x, y, width, height).unwrap()),
output_id: output_id.clone(),
mode: Cell::new(*mode),
refresh_nsec: Cell::new(mode.refresh_nsec()),
modes,
formats: CloneCell::new(Rc::new(vec![])),
format: Cell::new(XRGB8888),

View file

@ -4,6 +4,7 @@ pub mod dnd_icon;
pub mod ext_session_lock_surface_v1;
pub mod wl_subsurface;
pub mod wp_alpha_modifier_surface_v1;
pub mod wp_commit_timer_v1;
pub mod wp_fifo_v1;
pub mod wp_fractional_scale_v1;
pub mod wp_linux_drm_syncobj_surface_v1;
@ -47,6 +48,7 @@ use {
dnd_icon::DndIcon,
wl_subsurface::{PendingSubsurfaceData, SubsurfaceId, WlSubsurface},
wp_alpha_modifier_surface_v1::WpAlphaModifierSurfaceV1,
wp_commit_timer_v1::WpCommitTimerV1,
wp_fifo_v1::WpFifoV1,
wp_fractional_scale_v1::WpFractionalScaleV1,
wp_linux_drm_syncobj_surface_v1::WpLinuxDrmSyncobjSurfaceV1,
@ -60,14 +62,15 @@ use {
wp_presentation_feedback::WpPresentationFeedback,
zwp_linux_dmabuf_feedback_v1::ZwpLinuxDmabufFeedbackV1,
},
io_uring::IoUringError,
leaks::Tracker,
object::{Object, Version},
rect::{DamageQueue, Rect, Region},
renderer::Renderer,
tree::{
ContainerNode, FindTreeResult, FoundNode, LatchListener, Node, NodeId, NodeVisitor,
NodeVisitorBase, OutputNode, PlaceholderNode, PresentationListener, ToplevelNode,
VblankListener,
BeforeLatchListener, BeforeLatchResult, ContainerNode, FindTreeResult, FoundNode,
LatchListener, Node, NodeId, NodeVisitor, NodeVisitorBase, OutputNode, PlaceholderNode,
PresentationListener, ToplevelNode, VblankListener,
},
utils::{
cell_ext::CellExt, clonecell::CloneCell, copyhashmap::CopyHashMap,
@ -321,6 +324,8 @@ pub struct WlSurface {
latched_commit_version: Cell<u64>,
fifo: CloneCell<Option<Rc<WpFifoV1>>>,
clear_fifo_on_vblank: Cell<bool>,
commit_timer: CloneCell<Option<Rc<WpCommitTimerV1>>>,
before_latch_listener: EventListener<dyn BeforeLatchListener>,
}
impl Debug for WlSurface {
@ -444,6 +449,7 @@ struct PendingState {
explicit_sync: bool,
fifo_barrier_set: bool,
fifo_barrier_wait: bool,
commit_time: Option<u64>,
}
struct AttachedSubsurfaceState {
@ -494,6 +500,7 @@ impl PendingState {
opt!(tearing);
opt!(content_type);
opt!(alpha_multiplier);
opt!(commit_time);
{
let (dx1, dy1) = self.offset;
let (dx2, dy2) = mem::take(&mut next.offset);
@ -648,6 +655,8 @@ impl WlSurface {
latched_commit_version: Default::default(),
fifo: Default::default(),
clear_fifo_on_vblank: Default::default(),
commit_timer: Default::default(),
before_latch_listener: EventListener::new(slf.clone()),
}
}
@ -1657,6 +1666,7 @@ impl Object for WlSurface {
self.alpha_modifier.take();
self.text_input_connections.clear();
self.fifo.take();
self.commit_timer.take();
}
}
@ -2001,6 +2011,8 @@ pub enum WlSurfaceError {
CreateAsyncShmTexture(#[source] GfxError),
#[error("Could not prepare upload to a shm texture")]
PrepareAsyncUpload(#[source] GfxError),
#[error("Could not register a commit timeout")]
RegisterCommitTimeout(#[source] IoUringError),
}
efrom!(WlSurfaceError, ClientError);
efrom!(WlSurfaceError, XdgSurfaceError);
@ -2134,6 +2146,12 @@ impl VblankListener for WlSurface {
}
}
impl BeforeLatchListener for WlSurface {
fn before_latch(self: Rc<Self>, present: u64) -> BeforeLatchResult {
self.commit_timeline.before_latch(&self, present)
}
}
impl LatchListener for WlSurface {
fn after_latch(self: Rc<Self>, _on: &OutputNode, tearing: bool) {
if self.visible.get() {

View file

@ -7,7 +7,10 @@ use {
wl_buffer::WlBufferStorage,
wl_surface::{PendingState, WlSurface, WlSurfaceError},
},
io_uring::{IoUring, IoUringError, PendingPoll, PollCallback},
io_uring::{
IoUring, IoUringError, PendingPoll, PendingTimeout, PollCallback, TimeoutCallback,
},
tree::BeforeLatchResult,
utils::{
clonecell::CloneCell,
copyhashmap::CopyHashMap,
@ -28,7 +31,7 @@ use {
std::{
cell::{Cell, RefCell},
mem,
ops::DerefMut,
ops::{Deref, DerefMut},
rc::{Rc, Weak},
slice,
},
@ -50,6 +53,11 @@ pub struct CommitTimelines {
_flush_requests_future: SpawnedFuture<()>,
}
struct CommitTimeWaiter {
node: NodeRef<Entry>,
present: u64,
}
pub struct CommitTimeline {
shared: Rc<CommitTimelines>,
own_timeline: Rc<Inner>,
@ -57,6 +65,7 @@ pub struct CommitTimeline {
effective_timeline_id: Cell<CommitTimelineId>,
fifo_barrier_set: Cell<bool>,
fifo_waiter: Cell<Option<NodeRef<Entry>>>,
commit_time_waiter: RefCell<Option<CommitTimeWaiter>>,
}
struct Inner {
@ -98,6 +107,8 @@ pub enum CommitTimelineError {
RegisterImplicitPoll(#[source] IoUringError),
#[error("Could not wait for a dmabuf to become idle")]
PollDmabuf(#[source] OsError),
#[error("Could not wait for the commit timeout")]
CommitTimeout(#[source] OsError),
}
impl CommitTimelines {
@ -136,6 +147,7 @@ impl CommitTimelines {
effective_timeline_id: Cell::new(id),
fifo_barrier_set: Cell::new(false),
fifo_waiter: Default::default(),
commit_time_waiter: Default::default(),
}
}
@ -168,6 +180,7 @@ impl CommitTimeline {
match reason {
ClearReason::BreakLoops => {
self.fifo_waiter.take();
self.commit_time_waiter.take();
break_loops(&self.own_timeline.entries)
}
ClearReason::Destroy => {
@ -191,13 +204,18 @@ impl CommitTimeline {
acquire_points: Default::default(),
shm_uploads: 0,
implicit_dmabufs: Default::default(),
commit_time: Default::default(),
};
collector.collect(pending);
let points = collector.acquire_points;
let pending_uploads = collector.shm_uploads;
let implicit_dmabufs = collector.implicit_dmabufs;
let has_dependencies =
points.is_not_empty() || pending_uploads > 0 || implicit_dmabufs.is_not_empty();
let commit_time = collector.commit_time;
let has_commit_time = commit_time > 0;
let has_dependencies = points.is_not_empty()
|| pending_uploads > 0
|| implicit_dmabufs.is_not_empty()
|| has_commit_time;
let must_be_queued = has_dependencies
|| self.own_timeline.entries.is_not_empty()
|| (pending.fifo_barrier_wait && self.fifo_barrier_set.get());
@ -227,6 +245,7 @@ impl CommitTimeline {
num_pending_polls: NumCell::new(implicit_dmabufs.len()),
pending_polls: Cell::new(Default::default()),
fifo_state: Cell::new(commit_fifo_state),
commit_times: RefCell::new(CommitTimesState::Ready),
}),
);
let mut needs_flush = commit_fifo_state == CommitFifoState::Queued;
@ -263,6 +282,13 @@ impl CommitTimeline {
}
commit.pending_polls.set(pending_polls);
}
if has_commit_time {
*commit.commit_times.borrow_mut() = CommitTimesState::Queued {
rc: noderef.clone(),
time: commit_time,
};
needs_flush = true;
}
}
if needs_flush && noderef.prev().is_none() {
flush_from(noderef.clone()).map_err(CommitTimelineError::DelayedCommit)?;
@ -284,6 +310,30 @@ impl CommitTimeline {
pub fn has_fifo_barrier(&self) -> bool {
self.fifo_barrier_set.get()
}
pub fn before_latch(&self, surface: &WlSurface, present: u64) -> BeforeLatchResult {
let waiter = &mut *self.commit_time_waiter.borrow_mut();
if let Some(w) = waiter {
if w.present <= present {
let EntryKind::Commit(c) = &w.node.kind else {
unreachable!();
};
*c.commit_times.borrow_mut() = CommitTimesState::Ready;
self.shared
.flush_requests
.flush_waiters
.push(w.node.clone());
*waiter = None;
surface.before_latch_listener.detach();
BeforeLatchResult::Yield
} else {
BeforeLatchResult::None
}
} else {
surface.before_latch_listener.detach();
BeforeLatchResult::None
}
}
}
impl SyncObjWaiter for NodeRef<Entry> {
@ -343,6 +393,25 @@ impl PollCallback for NodeRef<Entry> {
}
}
impl TimeoutCallback for NodeRef<Entry> {
fn completed(self: Rc<Self>, res: Result<(), OsError>, _data: u64) {
let EntryKind::Commit(commit) = &self.kind else {
unreachable!();
};
commit.surface.commit_timeline.commit_time_waiter.take();
commit.surface.before_latch_listener.detach();
if let Err(e) = res {
commit
.surface
.client
.error(CommitTimelineError::CommitTimeout(e));
return;
}
*commit.commit_times.borrow_mut() = CommitTimesState::Ready;
flush_commit(&self, commit);
}
}
struct Entry {
link: Cell<Option<LinkedNode<Entry>>>,
shared: Rc<CommitTimelines>,
@ -362,6 +431,12 @@ enum ShmUploadState {
Scheduled(#[expect(dead_code)] SmallVec<[PendingShmTransfer; 1]>),
}
enum CommitTimesState {
Ready,
Queued { rc: Rc<NodeRef<Entry>>, time: u64 },
Registered { _pending: PendingTimeout },
}
struct Commit {
surface: Rc<WlSurface>,
pending: RefCell<Box<PendingState>>,
@ -372,6 +447,7 @@ struct Commit {
num_pending_polls: NumCell<usize>,
pending_polls: Cell<SmallVec<[PendingPoll; 1]>>,
fifo_state: Cell<CommitFifoState>,
commit_times: RefCell<CommitTimesState>,
}
fn flush_from(mut point: NodeRef<Entry>) -> Result<(), WlSurfaceError> {
@ -421,6 +497,19 @@ impl NodeRef<Entry> {
CommitFifoState::Mailbox => {}
}
}
let commit_times = &mut *c.commit_times.borrow_mut();
match commit_times {
CommitTimesState::Ready => {}
CommitTimesState::Queued { rc, time } => {
*commit_times = register_commit_time(tl, rc, c, *time)?;
if let CommitTimesState::Registered { .. } = commit_times {
has_unmet_dependencies = true;
}
}
CommitTimesState::Registered { .. } => {
has_unmet_dependencies = true;
}
}
if has_unmet_dependencies {
return Ok(false);
}
@ -455,6 +544,36 @@ fn check_shm_uploads(c: &Commit) -> Result<(), WlSurfaceError> {
Ok(())
}
fn register_commit_time(
tl: &CommitTimeline,
rc: &Rc<NodeRef<Entry>>,
c: &Commit,
time: u64,
) -> Result<CommitTimesState, WlSurfaceError> {
let output = c.surface.output.get();
let render_margin = output.render_margin_ns.get();
let flip_margin = output.flip_margin_ns.get().unwrap_or_default();
let refresh = output.global.refresh_nsec.get();
let present_margin = render_margin.saturating_add(flip_margin).min(refresh);
let timeout = time.saturating_sub(present_margin);
if timeout <= c.surface.client.state.now_nsec() {
return Ok(CommitTimesState::Ready);
}
let pending = tl
.shared
.ring
.timeout_external(timeout, rc.clone(), 0)
.map_err(WlSurfaceError::RegisterCommitTimeout)?;
*tl.commit_time_waiter.borrow_mut() = Some(CommitTimeWaiter {
node: rc.deref().clone(),
present: time,
});
c.surface
.before_latch_listener
.attach(&output.before_latch_event);
Ok(CommitTimesState::Registered { _pending: pending })
}
fn schedule_async_uploads(
node_ref: &Rc<NodeRef<Entry>>,
surface: &WlSurface,
@ -554,6 +673,7 @@ struct CommitDataCollector {
acquire_points: SmallVec<[Point; 1]>,
shm_uploads: usize,
implicit_dmabufs: SmallVec<[Rc<OwnedFd>; 1]>,
commit_time: u64,
}
impl CommitDataCollector {
@ -573,6 +693,9 @@ impl CommitDataCollector {
if let Some(point) = pending.acquire_point.take() {
self.acquire_points.push(point);
}
if let Some(commit_time) = pending.commit_time.take() {
self.commit_time = self.commit_time.max(commit_time);
}
for ss in pending.subsurfaces.values_mut() {
if let Some(state) = &mut ss.pending.state {
self.collect(state);

View file

@ -0,0 +1,94 @@
use {
crate::{
client::{Client, ClientError},
ifs::wl_surface::WlSurface,
leaks::Tracker,
object::{Object, Version},
wire::{
wp_commit_timer_v1::{Destroy, SetTimestamp, WpCommitTimerV1RequestHandler},
WpCommitTimerV1Id,
},
},
std::rc::Rc,
thiserror::Error,
};
pub struct WpCommitTimerV1 {
pub id: WpCommitTimerV1Id,
pub client: Rc<Client>,
pub surface: Rc<WlSurface>,
pub tracker: Tracker<Self>,
pub version: Version,
}
impl WpCommitTimerV1 {
pub fn new(id: WpCommitTimerV1Id, version: Version, surface: &Rc<WlSurface>) -> Self {
Self {
id,
client: surface.client.clone(),
surface: surface.clone(),
tracker: Default::default(),
version,
}
}
pub fn install(self: &Rc<Self>) -> Result<(), WpCommitTimerV1Error> {
if self.surface.commit_timer.is_some() {
return Err(WpCommitTimerV1Error::Exists);
}
self.surface.commit_timer.set(Some(self.clone()));
Ok(())
}
}
impl WpCommitTimerV1RequestHandler for WpCommitTimerV1 {
type Error = WpCommitTimerV1Error;
fn set_timestamp(&self, req: SetTimestamp, _slf: &Rc<Self>) -> Result<(), Self::Error> {
if req.tv_nsec >= 1_000_000_000 {
return Err(WpCommitTimerV1Error::InvalidNsec);
}
let nsec = (((req.tv_sec_hi as u64) << 32) | (req.tv_sec_lo as u64))
.checked_mul(1_000_000_000)
.and_then(|n| n.checked_add(req.tv_nsec as u64));
let Some(nsec) = nsec else {
return Err(WpCommitTimerV1Error::Overflow);
};
let pending = &mut *self.surface.pending.borrow_mut();
if pending.commit_time.is_some() {
return Err(WpCommitTimerV1Error::TimestampExists);
}
pending.commit_time = Some(nsec);
Ok(())
}
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.surface.commit_timer.take();
self.client.remove_obj(self)?;
Ok(())
}
}
object_base! {
self = WpCommitTimerV1;
version = self.version;
}
impl Object for WpCommitTimerV1 {}
simple_add_obj!(WpCommitTimerV1);
#[derive(Debug, Error)]
pub enum WpCommitTimerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error("The surface already has a commit timer extension attached")]
Exists,
#[error("The tv_nsec is larger than 999_999_999")]
InvalidNsec,
#[error("The timestamp overflowed")]
Overflow,
#[error("The commit already has a timestamp")]
TimestampExists,
}
efrom!(WpCommitTimerV1Error, ClientError);

View file

@ -0,0 +1,105 @@
use {
crate::{
client::{Client, ClientError},
globals::{Global, GlobalName},
ifs::wl_surface::wp_commit_timer_v1::{WpCommitTimerV1, WpCommitTimerV1Error},
leaks::Tracker,
object::{Object, Version},
wire::{
wp_commit_timing_manager_v1::{
Destroy, GetTimer, WpCommitTimingManagerV1RequestHandler,
},
WpCommitTimingManagerV1Id,
},
},
std::rc::Rc,
thiserror::Error,
};
pub struct WpCommitTimingManagerV1Global {
pub name: GlobalName,
}
pub struct WpCommitTimingManagerV1 {
pub id: WpCommitTimingManagerV1Id,
pub client: Rc<Client>,
pub tracker: Tracker<Self>,
pub version: Version,
}
impl WpCommitTimingManagerV1Global {
pub fn new(name: GlobalName) -> Self {
Self { name }
}
fn bind_(
self: Rc<Self>,
id: WpCommitTimingManagerV1Id,
client: &Rc<Client>,
version: Version,
) -> Result<(), WpCommitTimingManagerV1Error> {
let obj = Rc::new(WpCommitTimingManagerV1 {
id,
client: client.clone(),
tracker: Default::default(),
version,
});
track!(client, obj);
client.add_client_obj(&obj)?;
Ok(())
}
}
global_base!(
WpCommitTimingManagerV1Global,
WpCommitTimingManagerV1,
WpCommitTimingManagerV1Error
);
impl Global for WpCommitTimingManagerV1Global {
fn singleton(&self) -> bool {
true
}
fn version(&self) -> u32 {
1
}
}
simple_add_global!(WpCommitTimingManagerV1Global);
impl WpCommitTimingManagerV1RequestHandler for WpCommitTimingManagerV1 {
type Error = WpCommitTimingManagerV1Error;
fn destroy(&self, _req: Destroy, _slf: &Rc<Self>) -> Result<(), Self::Error> {
self.client.remove_obj(self)?;
Ok(())
}
fn get_timer(&self, req: GetTimer, _slf: &Rc<Self>) -> Result<(), Self::Error> {
let surface = self.client.lookup(req.surface)?;
let obj = Rc::new(WpCommitTimerV1::new(req.id, self.version, &surface));
track!(self.client, obj);
obj.install()?;
self.client.add_client_obj(&obj)?;
Ok(())
}
}
object_base! {
self = WpCommitTimingManagerV1;
version = self.version;
}
impl Object for WpCommitTimingManagerV1 {}
simple_add_obj!(WpCommitTimingManagerV1);
#[derive(Debug, Error)]
pub enum WpCommitTimingManagerV1Error {
#[error(transparent)]
ClientError(Box<ClientError>),
#[error(transparent)]
WpCommitTimerV1Error(#[from] WpCommitTimerV1Error),
}
efrom!(WpCommitTimingManagerV1Error, ClientError);