From 2973069bd0e9b1627fe349ce3e2c3ddfb4914f2f Mon Sep 17 00:00:00 2001 From: hujianwei Date: Tue, 22 Nov 2022 10:58:26 +0000 Subject: [PATCH] * sync_source.c: New file. --- sync_source.c | 575 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 575 insertions(+) create mode 100644 sync_source.c diff --git a/sync_source.c b/sync_source.c new file mode 100644 index 0000000..5a1a661 --- /dev/null +++ b/sync_source.c @@ -0,0 +1,575 @@ +/* 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; + } + +#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, switch the renderer to + vsync mode. */ + + if (!RenderSetRenderMode (helper->target, RenderModeVsync, + 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