/* Wayland compositor running on top of an X server. Copyright (C) 2022 to various contributors. This file is part of 12to11. 12to11 is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. 12to11 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with 12to11. If not, see . */ #include #include "compositor.h" /* Generic sync helper. There are two methods for the protocol translator to synchronize its redraw with the X compositing manager and server; the protocol translator dynamically selects the right method depending on which options have been specified by the client upon commit. The first is based on the _NET_WM_SYNC_REQUEST and _NET_WM_FRAME_DRAWN protocols, and is only present when there is a compositing manager. The second is not yet implemented. */ enum _SynchronizationType { SyncTypeFrameClock, SyncTypePresent, }; struct _SyncHelper { /* The pending frame ID. */ uint64_t pending_frame; /* The associated subcompositor. */ Subcompositor *subcompositor; /* The associated rendering target. */ RenderTarget target; /* The associated window. */ Window window; /* The associated frame clock. */ FrameClock *clock; /* Callback called to run frame callbacks. */ void (*frame_callback) (void *, uint32_t); /* Callback called to start resize. */ void (*resize_callback) (void *, Bool); /* Callback called to decide whether or not it is ok to fast forward a frame. */ Bool (*fast_forward_callback) (void *); /* Role associated with the sync helper. */ Role *role; /* Clock synchronization part of sync helper. The sync helper has to seamlessly switch between two different clocks: the monotonic X server time, in microseconds, and the system time. The switching is done by maintaining two counters. The first is a 64-bit microsecond-precision counter containing the last reported time with the milliseconds part truncated to 32 bits, and the second is the struct timespec at which that time arrived. */ uint64_t server_time, arrival_time; /* What kind of synchronization is being used. Switching to a given synchronization type can only happen when starting a frame. */ SynchronizationType used; /* The last msc and ust. This is only set upon ModePresented, and used to drive async presentation. */ uint64_t last_msc, last_ust; /* Various flags. */ int flags; }; enum { FrameStarted = 1, FramePending = 1 << 1, FrameSynchronized = 1 << 2, FrameResize = 1 << 3, }; static SynchronizationType GetWantedSynchronizationType (SyncHelper *helper) { if (helper->flags & FrameSynchronized) return SyncTypeFrameClock; if (helper->flags & FrameResize) { /* Confirming a resize must be done using the regular frame clock. */ helper->flags &= ~FrameResize; return SyncTypeFrameClock; } /* If tearing is allowed, then use present targeting the next frame. */ if (helper->role->surface && (helper->role->surface->current_state.presentation_hint == PresentationHintAsync)) return SyncTypePresent; #ifdef AllowPresent /* TODO: make this work. */ /* Otherwise, use Present. */ return SyncTypePresent; #else return SyncTypeFrameClock; #endif } static uint64_t ServerTimeFromTimespec (struct timespec *clock) { uint64_t timestamp; if (IntMultiplyWrapv (clock->tv_sec, 1000000, ×tamp) || IntAddWrapv (timestamp, clock->tv_nsec / 1000, ×tamp)) /* Overflow. */ return 0; return timestamp; } static uint64_t ConfineTime (uint64_t time) { uint32_t milliseconds; /* Given a microsecond time, confine the millisecond part to CARD32. */ milliseconds = time / 1000; return (milliseconds * (uint64_t) 1000 + time % 1000); } static Bool TimestampGreaterThan (uint64_t time_a, uint64_t time_b) { uint32_t ms_a, ms_b; /* Compare the millisecond part, handling wraparound. */ ms_a = time_a / 1000; ms_b = time_b / 1000; if (ms_a > ms_b) return ms_b - ms_a > UINT32_MAX / 2; if (ms_a == ms_b) return (time_a % 1000 > time_b % 1000); return ms_b - ms_a > UINT32_MAX / 2; } static uint64_t ConsiderFrameTime (SyncHelper *helper, uint64_t frame_time_us) { struct timespec current_time; uint64_t old_arrival_time; /* Get the time the previous timestamp arrived for future reference. */ old_arrival_time = helper->arrival_time; /* Store the time at which the specified timestamp arrived. */ clock_gettime (CLOCK_MONOTONIC, ¤t_time); helper->arrival_time = ServerTimeFromTimespec (¤t_time); if (frame_time_us == (uint64_t) -1) { use_future_time: /* Some frame time in the future should be computed and used. */ if (IntAddWrapv (helper->server_time, helper->arrival_time - old_arrival_time, &helper->server_time)) return (uint64_t) -1; helper->server_time = ConfineTime (helper->server_time); return helper->server_time; } /* If frame_time_us is larger than helper->server_time, great! Just use it instead. */ if (TimestampGreaterThan (frame_time_us, helper->server_time)) { helper->server_time = ConfineTime (frame_time_us); return helper->server_time; } /* Otherwise, go to the -1 branch. */ goto use_future_time; } static void FrameCompleted (SyncHelper *helper, uint64_t frame_time_us) { uint64_t time; /* The frame completed. Run frame callbacks should there be no pending frame, or start a new update again mailbox-style. */ time = ConsiderFrameTime (helper, frame_time_us); if (helper->flags & FramePending) { /* Clear the frame pending flag. */ helper->flags &= ~FramePending; /* Start a new update. */ SubcompositorUpdate (helper->subcompositor); } else /* Run the frame callback. */ helper->frame_callback (helper->role, time / 1000); } static void EndFrame (SyncHelper *helper) { XLFrameClockEndFrame (helper->clock); } static Bool UpdateFrameRefreshPrediction (SyncHelper *helper) { int desync_children; /* Count the number of desynchronous children attached to this surface, directly or indirectly. When this number is more than 1, enable frame refresh prediction, which allows separate frames from subsurfaces to be batched together. */ if (helper->role->surface) { desync_children = 0; XLUpdateDesynchronousChildren (helper->role->surface, &desync_children); if (desync_children) XLFrameClockSetPredictRefresh (helper->clock); else XLFrameClockDisablePredictRefresh (helper->clock); return desync_children > 0; } return False; } static void NoteFrame (FrameMode mode, uint64_t id, void *data, uint64_t msc, uint64_t ust) { SyncHelper *helper; Bool success; SynchronizationType wanted; helper = data; switch (mode) { case ModeStarted: /* Record this frame counter as the pending frame. */ helper->pending_frame = id; if (helper->flags & FrameStarted) break; helper->flags |= FrameStarted; wanted = GetWantedSynchronizationType (helper); if (wanted == SyncTypeFrameClock) { frame_clock: /* Check whether or not frame refresh prediction should be used. */ UpdateFrameRefreshPrediction (helper); /* Use async presentation. */ RenderSetRenderMode (helper->target, RenderModeAsync, helper->last_msc + 1); /* Start frame clock-based synchronization. If I were more confident in this code, then the call would be allowed to fail, but as it stands I'm not. */ success = XLFrameClockStartFrame (helper->clock, False); XLAssert (success); helper->flags |= FrameSynchronized; } else if (wanted == SyncTypePresent) { /* Since presentation is wanted (which can currently only be due to the client having requested a presentation hint of "async"), switch the renderer to vsync-async mode. */ if (!RenderSetRenderMode (helper->target, RenderModeVsyncAsync, helper->last_msc + 1)) { wanted = SyncTypeFrameClock; goto frame_clock; } /* Now, presentation will implicitly be synchronized. */ } helper->used = wanted; break; case ModePresented: helper->last_msc = msc; helper->last_ust = ust; Fallthrough; case ModeComplete: /* The frame was completed. */ if (id == helper->pending_frame) { /* End the frame if a frame clock was used for synchronization. */ if (helper->used == SyncTypeFrameClock) EndFrame (helper); /* Clear the frame completed flag. FrameSynchronized will still be set, until AfterFrame is called. */ helper->flags &= ~FrameStarted; if (!(helper->flags & FrameSynchronized)) /* But this means the frame was not synchronized. Run frame callbacks or start a new update now. */ FrameCompleted (helper, (uint64_t) -1); /* This value means that there is no frame currently being displayed. */ helper->pending_frame = (uint64_t) -1; } break; default: } } static void AfterFrame (FrameClock *clock, void *data, uint64_t frame_time_us) { SyncHelper *helper; helper = data; /* The frame completed. */ helper->flags &= ~FrameSynchronized; FrameCompleted (helper, frame_time_us); } static void HandleFreeze (void *data, Bool only_frame) { SyncHelper *helper; helper = data; /* The helper is now frozen. Cancel any late frame and run the resize callback. Make sure that the next update will be done via the frame clock. */ helper->flags &= ~FramePending; helper->flags |= FrameResize; if (helper->resize_callback) helper->resize_callback (helper->role, only_frame); } static Bool CheckFrame (SyncHelper *helper) { Bool rc; /* Return whether or not it is ok to perform an update now. It is not ok when frame-clock synchronization is being used, and the compositing manager is reading from the contents of the back buffer. */ rc = (helper->used != SyncTypeFrameClock || !XLFrameClockFrameInProgress (helper->clock) || XLFrameClockCanBatch (helper->clock)); return rc; } static Bool QueryFastForward (void *data) { SyncHelper *helper; helper = data; if (helper->fast_forward_callback) return helper->fast_forward_callback (helper->role); return False; } SyncHelper * MakeSyncHelper (Subcompositor *subcompositor, Window window, RenderTarget target, void (*frame_callback) (void *, uint32_t), Role *role) { SyncHelper *helper; struct timespec current_time; /* Create a sync helper for the given subcompositor, which synchronizes to the specified window. */ helper = XLCalloc (1, sizeof *helper); helper->clock = XLMakeFrameClockForWindow (window); helper->subcompositor = subcompositor; helper->pending_frame = -1; helper->frame_callback = frame_callback; helper->role = role; helper->target = target; /* Set the note frame callback. */ SubcompositorSetNoteFrameCallback (helper->subcompositor, NoteFrame, helper); /* Set the frame clock callbacks. */ XLFrameClockAfterFrame (helper->clock, AfterFrame, helper); XLFrameClockSetFreezeCallback (helper->clock, HandleFreeze, QueryFastForward, helper); /* Initialize the sync helper time. */ clock_gettime (CLOCK_MONOTONIC, ¤t_time); helper->arrival_time = ServerTimeFromTimespec (¤t_time) + 0xffffffff*1000; if (compositor.server_time_monotonic) helper->server_time = helper->arrival_time; else /* This can never overflow because the X server time is limited to 0xffffffff. */ helper->server_time = XLGetServerTimeRoundtrip () * 1000; return helper; } void SyncHelperUpdate (SyncHelper *helper) { /* Perform a subcompositor update on helper. If the update will happen while the compositing manager is still drawing the results, schedule the update for when the frame completes. */ if (!CheckFrame (helper)) helper->flags |= FramePending; else SubcompositorUpdate (helper->subcompositor); } void FreeSyncHelper (SyncHelper *helper) { XLFreeFrameClock (helper->clock); SubcompositorSetNoteFrameCallback (helper->subcompositor, NULL, NULL); XLFree (helper); } void SyncHelperHandleFrameEvent (SyncHelper *helper, XEvent *event) { XLFrameClockHandleFrameEvent (helper->clock, event); } /* Much of the code below is only necessary in the xdg_toplevel role. */ void SyncHelperSetResizeCallback (SyncHelper *helper, void (*resize_start) (void *, Bool), Bool (*check_fast_forward) (void *)) { /* Set a resize callback. It is called to begin a resize. Upon being called, the sync helper becomes "frozen", and will not display frames until the next call to SyncHelperUpdate. */ helper->resize_callback = resize_start; helper->fast_forward_callback = check_fast_forward; } void SyncHelperNoteConfigureEvent (SyncHelper *helper) { /* Tell the frame clock about the arrival of a ConfigureNotify event. This is used to determine whether a synchronization event is up-to-date. */ XLFrameClockNoteConfigure (helper->clock); } void SyncHelperCheckFrameCallback (SyncHelper *helper) { uint64_t time; /* Prevent deadlocks when the client is waiting for a frame callback while the frame clock is frozen, which can happen if it submits frame callbacks before calling Commit. If the frame clock is frozen, meaning that a resize is in progress, generate a frame. */ time = ConsiderFrameTime (helper, -1); helper->frame_callback (helper->role, time / 1000); } void SyncHelperClearPendingFrame (SyncHelper *helper) { /* Clear any frame that is waiting to be displayed. This should be called prior to a configure event for clients which must handle interactive resize. */ helper->flags &= ~FramePending; } #if 0 static void __attribute__((constructor)) SyncHelperSelftest (void) { uint64_t timestampa, timestampb; timestampa = 1000 * 1000 + 500; timestampb = 1000 * 1000 + 550; XLAssert (TimestampGreaterThan (timestampb, timestampa)); XLAssert (!TimestampGreaterThan (timestampa, timestampb)); timestampa = (uint64_t) 0xffffffff * 1000 + 500; timestampb = (uint64_t) 0xffffffff * 1000 + 550; XLAssert (TimestampGreaterThan (timestampb, timestampa)); XLAssert (!TimestampGreaterThan (timestampa, timestampb)); timestampa = (uint64_t) 0xffffffff * 1000 + 500; timestampb = (((uint64_t) 0xffffffff) + 1) * 1000 + 500; XLAssert (TimestampGreaterThan (timestampb, timestampa)); XLAssert (!TimestampGreaterThan (timestampa, timestampb)); } #endif