wayland: implement commit-timing-v1
This commit is contained in:
parent
d45aaffdb3
commit
fac5445f2e
19 changed files with 434 additions and 15 deletions
|
|
@ -156,6 +156,7 @@ Jay supports the following wayland protocols:
|
|||
| wl_shm | 2 | |
|
||||
| wl_subcompositor | 1 | |
|
||||
| wp_alpha_modifier_v1 | 1 | |
|
||||
| wp_commit_timing_manager_v1 | 1 | |
|
||||
| wp_content_type_manager_v1 | 1 | |
|
||||
| wp_cursor_shape_manager_v1 | 1 | |
|
||||
| wp_drm_lease_device_v1 | 1 | |
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
- Implement screencast session restoration.
|
||||
- Fix screen sharing in zoom.
|
||||
- Implement wp-fifo-v1.
|
||||
- Implement wp-commit-timing-v1.
|
||||
|
||||
# 1.6.0 (2024-09-25)
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,15 @@ pub struct Mode {
|
|||
pub refresh_rate_millihz: u32,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn refresh_nsec(&self) -> u64 {
|
||||
match self.refresh_rate_millihz {
|
||||
0 => u64::MAX,
|
||||
n => 1_000_000_000_000 / (n as u64),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MonitorInfo {
|
||||
pub modes: Vec<Mode>,
|
||||
|
|
|
|||
|
|
@ -101,6 +101,9 @@ impl MetalConnector {
|
|||
if !self.can_present.get() {
|
||||
continue;
|
||||
}
|
||||
let Some(node) = self.state.root.outputs.get(&self.connector_id) else {
|
||||
continue;
|
||||
};
|
||||
let mut expected_sequence = self.sequence.get() + 1;
|
||||
let mut start = Time::now_unchecked();
|
||||
let use_frame_scheduling = !self.try_async_flip();
|
||||
|
|
@ -118,7 +121,15 @@ impl MetalConnector {
|
|||
}
|
||||
}
|
||||
frame!(frame_name);
|
||||
if let Err(e) = self.present_once().await {
|
||||
{
|
||||
let now = start.nsec();
|
||||
let flip = match self.try_async_flip() {
|
||||
true => now,
|
||||
false => self.next_vblank_nsec.get(),
|
||||
};
|
||||
node.before_latch(flip).await;
|
||||
}
|
||||
if let Err(e) = self.present_once(&node).await {
|
||||
log::error!("Could not present: {}", ErrorFmt(e));
|
||||
continue;
|
||||
}
|
||||
|
|
@ -138,7 +149,7 @@ impl MetalConnector {
|
|||
}
|
||||
}
|
||||
|
||||
async fn present_once(&self) -> Result<(), MetalError> {
|
||||
async fn present_once(&self, node: &Rc<OutputNode>) -> Result<(), MetalError> {
|
||||
let version = self.version.get();
|
||||
if !self.can_present.get() {
|
||||
return Ok(());
|
||||
|
|
@ -146,9 +157,6 @@ impl MetalConnector {
|
|||
if !self.backend.check_render_context(&self.dev) {
|
||||
return Ok(());
|
||||
}
|
||||
let Some(node) = self.state.root.outputs.get(&self.connector_id) else {
|
||||
return Ok(());
|
||||
};
|
||||
let crtc = match self.crtc.get() {
|
||||
Some(crtc) => crtc,
|
||||
_ => return Ok(()),
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use {
|
|||
gfx_api::{AcquireSync, GfxContext, GfxError, GfxFramebuffer, GfxTexture, ReleaseSync},
|
||||
ifs::wl_output::OutputId,
|
||||
state::State,
|
||||
time::Time,
|
||||
utils::{
|
||||
clonecell::CloneCell, copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell,
|
||||
queue::AsyncQueue, syncqueue::SyncQueue,
|
||||
|
|
@ -745,6 +746,8 @@ impl XBackend {
|
|||
image.last_serial.set(serial);
|
||||
|
||||
if let Some(node) = self.state.root.outputs.get(&output.id) {
|
||||
let now = Time::now_unchecked().nsec();
|
||||
node.before_latch(now).await;
|
||||
let res = self.state.present_output(
|
||||
&node,
|
||||
&image.fb.get(),
|
||||
|
|
|
|||
|
|
@ -548,7 +548,7 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
&backend::Mode {
|
||||
width: 0,
|
||||
height: 0,
|
||||
refresh_rate_millihz: 0,
|
||||
refresh_rate_millihz: 40_000,
|
||||
},
|
||||
0,
|
||||
0,
|
||||
|
|
@ -582,8 +582,10 @@ fn create_dummy_output(state: &Rc<State>) {
|
|||
vblank_event: Default::default(),
|
||||
latch_event: Default::default(),
|
||||
presentation_event: Default::default(),
|
||||
render_margin_ns: Default::default(),
|
||||
flip_margin_ns: Default::default(),
|
||||
ext_copy_sessions: Default::default(),
|
||||
before_latch_event: Default::default(),
|
||||
});
|
||||
let dummy_workspace = Rc::new(WorkspaceNode {
|
||||
id: state.node_ids.next(),
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ use {
|
|||
wl_subcompositor::WlSubcompositorGlobal,
|
||||
wl_surface::xwayland_shell_v1::XwaylandShellV1Global,
|
||||
wp_alpha_modifier_v1::WpAlphaModifierV1Global,
|
||||
wp_commit_timing_manager_v1::WpCommitTimingManagerV1Global,
|
||||
wp_content_type_manager_v1::WpContentTypeManagerV1Global,
|
||||
wp_cursor_shape_manager_v1::WpCursorShapeManagerV1Global,
|
||||
wp_fifo_manager_v1::WpFifoManagerV1Global,
|
||||
|
|
@ -205,6 +206,7 @@ impl Globals {
|
|||
add_singleton!(ExtForeignToplevelImageCaptureSourceManagerV1Global);
|
||||
add_singleton!(ExtImageCopyCaptureManagerV1Global);
|
||||
add_singleton!(WpFifoManagerV1Global);
|
||||
add_singleton!(WpCommitTimingManagerV1Global);
|
||||
}
|
||||
|
||||
pub fn add_backend_singletons(&self, backend: &Rc<dyn Backend>) {
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ pub mod wl_shm_pool;
|
|||
pub mod wl_subcompositor;
|
||||
pub mod wl_surface;
|
||||
pub mod wp_alpha_modifier_v1;
|
||||
pub mod wp_commit_timing_manager_v1;
|
||||
pub mod wp_content_type_manager_v1;
|
||||
pub mod wp_content_type_v1;
|
||||
pub mod wp_cursor_shape_device_v1;
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
94
src/ifs/wl_surface/wp_commit_timer_v1.rs
Normal file
94
src/ifs/wl_surface/wp_commit_timer_v1.rs
Normal 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);
|
||||
105
src/ifs/wp_commit_timing_manager_v1.rs
Normal file
105
src/ifs/wp_commit_timing_manager_v1.rs
Normal 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);
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
pub use ops::{
|
||||
poll_external::{PendingPoll, PollCallback},
|
||||
timeout_external::{PendingTimeout, TimeoutCallback},
|
||||
TaskResultExt,
|
||||
};
|
||||
use {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ pub struct TimeoutExternalTask {
|
|||
}
|
||||
|
||||
impl IoUring {
|
||||
#[expect(dead_code)]
|
||||
pub fn timeout_external(
|
||||
&self,
|
||||
timeout_nsec: u64,
|
||||
|
|
|
|||
|
|
@ -184,8 +184,10 @@ impl ConnectorHandler {
|
|||
latch_event: Default::default(),
|
||||
vblank_event: Default::default(),
|
||||
presentation_event: Default::default(),
|
||||
render_margin_ns: Default::default(),
|
||||
flip_margin_ns: Default::default(),
|
||||
ext_copy_sessions: Default::default(),
|
||||
before_latch_event: Default::default(),
|
||||
});
|
||||
on.update_visible();
|
||||
on.update_rects();
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ use {
|
|||
std::{
|
||||
cell::{Cell, RefCell},
|
||||
fmt::{Debug, Formatter},
|
||||
ops::Deref,
|
||||
ops::{BitOrAssign, Deref},
|
||||
rc::Rc,
|
||||
},
|
||||
};
|
||||
|
|
@ -89,9 +89,29 @@ pub struct OutputNode {
|
|||
pub latch_event: EventSource<dyn LatchListener>,
|
||||
pub vblank_event: EventSource<dyn VblankListener>,
|
||||
pub presentation_event: EventSource<dyn PresentationListener>,
|
||||
pub render_margin_ns: Cell<u64>,
|
||||
pub flip_margin_ns: Cell<Option<u64>>,
|
||||
pub ext_copy_sessions:
|
||||
CopyHashMap<(ClientId, ExtImageCopyCaptureSessionV1Id), Rc<ExtImageCopyCaptureSessionV1>>,
|
||||
pub before_latch_event: EventSource<dyn BeforeLatchListener>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub enum BeforeLatchResult {
|
||||
None,
|
||||
Yield,
|
||||
}
|
||||
|
||||
impl BitOrAssign for BeforeLatchResult {
|
||||
fn bitor_assign(&mut self, rhs: Self) {
|
||||
if rhs == BeforeLatchResult::Yield {
|
||||
*self = rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait BeforeLatchListener {
|
||||
fn before_latch(self: Rc<Self>, present: u64) -> BeforeLatchResult;
|
||||
}
|
||||
|
||||
pub trait LatchListener {
|
||||
|
|
@ -135,6 +155,16 @@ pub async fn output_render_data(state: Rc<State>) {
|
|||
}
|
||||
|
||||
impl OutputNode {
|
||||
pub async fn before_latch(&self, present: u64) {
|
||||
let mut res = BeforeLatchResult::None;
|
||||
for listener in self.before_latch_event.iter() {
|
||||
res |= listener.before_latch(present);
|
||||
}
|
||||
if res == BeforeLatchResult::Yield {
|
||||
self.state.eng.yield_now().await;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn latched(&self, tearing: bool) {
|
||||
self.schedule.latched();
|
||||
for listener in self.latch_event.iter() {
|
||||
|
|
@ -703,6 +733,7 @@ impl OutputNode {
|
|||
}
|
||||
let (old_width, old_height) = self.global.pixel_size();
|
||||
self.global.mode.set(mode);
|
||||
self.global.refresh_nsec.set(mode.refresh_nsec());
|
||||
self.global.persistent.transform.set(transform);
|
||||
let (new_width, new_height) = self.global.pixel_size();
|
||||
self.change_extents_(&self.calculate_extents());
|
||||
|
|
|
|||
9
wire/wp_commit_timer_v1.txt
Normal file
9
wire/wp_commit_timer_v1.txt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
request set_timestamp {
|
||||
tv_sec_hi: u32,
|
||||
tv_sec_lo: u32,
|
||||
tv_nsec: u32,
|
||||
}
|
||||
|
||||
request destroy {
|
||||
|
||||
}
|
||||
8
wire/wp_commit_timing_manager_v1.txt
Normal file
8
wire/wp_commit_timing_manager_v1.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
request destroy {
|
||||
|
||||
}
|
||||
|
||||
request get_timer {
|
||||
id: id(wp_commit_timer_v1),
|
||||
surface: id(wl_surface),
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue