From a075a2941f8fcdae155495da634cf63368c950b7 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Fri, 16 Oct 2026 09:33:39 +0300 Subject: Pace frame requests by the frame presented after them A frame request now only records intent. Each loop step resolves it after the callbacks that present, so a request made before the present waits for that frame. An animation that asked first got two REFRESH events per frame on X11 and with GL. With no frame in flight, the REFRESH comes one refresh interval after the last present or answer, so a request without a present cannot spin. Queued events now go before an owed REFRESH. Input reaches the application before it draws, and a queued REFRESH pays an owed one for the same surface. The loop prepare only peeks at a lost event, so deliver can report it as a final loss. A failed push from another thread wakes the loop. Signals and posts run at the end of a step, after descriptors and timers. A signal handler now sees the output that came with it. A render-thread GL swap counts as in flight on Wayland. Co-Authored-By: Claude Opus 5.5 --- src/common/backend.c | 72 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 22 deletions(-) (limited to 'src/common/backend.c') diff --git a/src/common/backend.c b/src/common/backend.c index 9910e08..1a81a7b 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -22,14 +22,17 @@ static const struct yasl_private_backend_ops *backend_ops(enum yasl_private_back } } +static void resolve_frame_requests(struct yasl_private_display *d); + /* The display drives the loop it runs on; see struct yasl_private_loop_driver. */ static bool driver_prepare(void *ud) { struct yasl_private_display *d = ud; bool ready = d->ops->display_prepare(d); + resolve_frame_requests(d); /* Readiness the backends cannot see: events already queued, an error to * report, or an owed REFRESH. */ - return ready || yasl_private_events_pending(d) || yasl_private_events_error(d) || + return ready || yasl_private_events_pending(d) || yasl_private_events_failed(d) || (d->pending && d->pending(d)); } @@ -584,43 +587,70 @@ void yasl_private_surface_request_frame(struct yasl_surface *s) yasl_private_loop_wake(s->display->loop); } +static uint64_t refresh_interval_ns(struct yasl_surface *s) +{ + double refresh = yasl_private_surface_timing(s).refresh_ns; + return refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667; +} + void yasl_private_surface_notify_frame_shown(struct yasl_surface *s) { if (!s || !s->frame_requested) return; s->frame_requested = false; + s->frame_answered_ns = yasl_private_loop_now_ns(); s->refresh_owed = true; } +static void resolve_frame_request(struct yasl_surface *s); + static uint64_t frame_interval_done(void *ud, yasl_private_timer_id id, uint64_t interval_ns) { (void)id; (void)interval_ns; - yasl_private_surface_notify_frame_shown(ud); + struct yasl_surface *s = ud; + s->frame_timer = 0; + /* A present since the timer was set moves the next frame later. */ + resolve_frame_request(s); return 0; } -bool yasl_private_surface_request_shown_frame(struct yasl_surface *s) +/* The frame a request waits for is the next one the application presents, + * which is often after the request. So the loop decides at the start of each + * step, after the callbacks that draw have run. */ +static void resolve_frame_request(struct yasl_surface *s) { - s->frame_requested = true; + if (!s->frame_requested || yasl_private_loop_timer_armed(s->display->loop, s->frame_timer)) + return; + /* The system reports when the frame in flight is shown. */ const struct yasl_private_backend_ops *ops = s->display->ops; - if (ops->surface_frame_in_flight) { - if (!ops->surface_frame_in_flight(s)) - yasl_private_surface_notify_frame_shown(s); - return true; - } - /* No frame signal: one frame per refresh interval since the last present. */ - if (yasl_private_loop_timer_armed(s->display->loop, s->frame_timer)) - return true; - double refresh = yasl_private_surface_timing(s).refresh_ns; - uint64_t interval = refresh >= 1000000.0 && refresh <= 1000000000.0 ? (uint64_t)refresh : 16666667; - uint64_t presented = atomic_load(&s->presented_ns); - uint64_t since = yasl_private_loop_now_ns() - presented; - if (!presented || since >= interval) { + if (ops->surface_frame_in_flight && ops->surface_frame_in_flight(s)) + return; + /* Otherwise one frame per refresh interval, counted from the last + * present or answer, so a request with no present cannot spin. */ + uint64_t last = atomic_load(&s->presented_ns); + if (s->frame_answered_ns > last) + last = s->frame_answered_ns; + uint64_t interval = refresh_interval_ns(s); + uint64_t since = yasl_private_loop_now_ns() - last; + if (!last || since >= interval) { yasl_private_surface_notify_frame_shown(s); - return true; + return; } s->frame_timer = yasl_private_surface_add_timer(s, interval - since, frame_interval_done, s); - return s->frame_timer != 0; + /* Without a timer, an early frame is better than none. */ + if (!s->frame_timer) + yasl_private_surface_notify_frame_shown(s); +} + +void yasl_private_surface_request_shown_frame(struct yasl_surface *s) +{ + s->frame_requested = true; +} + +static void resolve_frame_requests(struct yasl_private_display *d) +{ + for (struct yasl_surface *s = d->surfaces; s; s = s->event_next) + resolve_frame_request(s); } static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interval) @@ -628,9 +658,7 @@ static uint64_t pacing_tick(void *ud, yasl_private_timer_id id, uint64_t interva (void)id; (void)interval; struct yasl_surface *s = ud; yasl_private_surface_request_frame(s); - double refresh = yasl_private_surface_timing(s).refresh_ns; - return refresh >= 1000000.0 && refresh <= 1000000000.0 - ? (uint64_t)refresh : 16666667; + return refresh_interval_ns(s); } bool yasl_private_surface_set_pacing(struct yasl_surface *s, yasl_private_pacing_mode mode) -- cgit v1.2.3