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

@ -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 | |

View file

@ -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)

View file

@ -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>,

View file

@ -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(()),

View file

@ -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(),

View file

@ -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(),

View file

@ -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>) {

View file

@ -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;

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);

View file

@ -1,5 +1,6 @@
pub use ops::{
poll_external::{PendingPoll, PollCallback},
timeout_external::{PendingTimeout, TimeoutCallback},
TaskResultExt,
};
use {

View file

@ -45,7 +45,6 @@ pub struct TimeoutExternalTask {
}
impl IoUring {
#[expect(dead_code)]
pub fn timeout_external(
&self,
timeout_nsec: u64,

View file

@ -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();

View file

@ -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());

View file

@ -0,0 +1,9 @@
request set_timestamp {
tv_sec_hi: u32,
tv_sec_lo: u32,
tv_nsec: u32,
}
request destroy {
}

View file

@ -0,0 +1,8 @@
request destroy {
}
request get_timer {
id: id(wp_commit_timer_v1),
surface: id(wl_surface),
}