diff options
author | Mingi Sung | 2024-05-08 22:03:56 +0900 |
---|---|---|
committer | Mingi Sung | 2024-05-08 22:03:56 +0900 |
commit | 9d860be09e5db9aaa86febd9a3b8574d51d680d7 (patch) | |
tree | f2ebd1fa2ac32cabcaf54ae909616aa17a542c14 /mr1441.patch | |
parent | c35008e2a13891eb73763936a8a8ab7c80667bf1 (diff) | |
download | aur-9d860be09e5db9aaa86febd9a3b8574d51d680d7.tar.gz |
Update mr1441 and mr3729
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
Diffstat (limited to 'mr1441.patch')
-rw-r--r-- | mr1441.patch | 3523 |
1 files changed, 2357 insertions, 1166 deletions
diff --git a/mr1441.patch b/mr1441.patch index e4259b6d1097..b4041a3979ff 100644 --- a/mr1441.patch +++ b/mr1441.patch @@ -1,7 +1,6 @@ Author: Daniel van Vugt <daniel.van.vugt@canonical.com> Source: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441 -Commit: abe84f8ef3e075f7caae6ced8dfb6d536948d2d8 -Last Updated: 04/29/24 (Mutter 46.1) +Source: https://gitlab.gnome.org/Community/Ubuntu/mutter/-/tree/triple-buffering-v4-46 --- Use triple buffering if and when the previous frame is running late. @@ -15,794 +14,30 @@ If the previous frame is not running late then we stick to double buffering so there's no latency penalty when the system is able to maintain full frame rate. --- -diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c -index 93e4c9329020286d3c1202ef4f6420b2deb74942..b637fac637f956b2ae5eb71b755f4f00bf689062 100644 ---- a/clutter/clutter/clutter-frame-clock.c -+++ b/clutter/clutter/clutter-frame-clock.c -@@ -42,6 +42,15 @@ enum - - static guint signals[N_SIGNALS]; - -+typedef enum -+{ -+ TRIPLE_BUFFERING_MODE_NEVER, -+ TRIPLE_BUFFERING_MODE_AUTO, -+ TRIPLE_BUFFERING_MODE_ALWAYS, -+} TripleBufferingMode; -+ -+static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; -+ - #define SYNC_DELAY_FALLBACK_FRACTION 0.875 - - #define MINIMUM_REFRESH_RATE 30.f -@@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState - CLUTTER_FRAME_CLOCK_STATE_IDLE, - CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, - CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, -- CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, -- CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW, -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, - } ClutterFrameClockState; - - struct _ClutterFrameClock -@@ -92,6 +103,7 @@ struct _ClutterFrameClock - ClutterFrameClockMode mode; - - int64_t last_dispatch_time_us; -+ int64_t prev_last_dispatch_time_us; - int64_t last_dispatch_lateness_us; - int64_t last_presentation_time_us; - int64_t next_update_time_us; -@@ -111,6 +123,9 @@ struct _ClutterFrameClock - int64_t vblank_duration_us; - /* Last KMS buffer submission time. */ - int64_t last_flip_time_us; -+ int64_t prev_last_flip_time_us; -+ -+ ClutterFrameHint last_flip_hints; - - /* Last time we promoted short-term maximum to long-term one */ - int64_t longterm_promotion_us; -@@ -245,10 +260,6 @@ static void - maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, - ClutterFrameInfo *frame_info) - { -- /* Do not update long-term max if there has been no measurement */ -- if (!frame_clock->shortterm_max_update_duration_us) -- return; -- - if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) < - G_USEC_PER_SEC) - return; -@@ -275,6 +286,12 @@ void - clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - ClutterFrameInfo *frame_info) - { -+#ifdef CLUTTER_ENABLE_DEBUG -+ const char *debug_state = -+ frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ? -+ "Triple buffering" : "Double buffering"; -+#endif -+ - COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, - "Clutter::FrameClock::presented()"); - COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, -@@ -361,22 +378,52 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - - frame_clock->got_measurements_last_frame = FALSE; - -- if (frame_info->cpu_time_before_buffer_swap_us != 0 && -- frame_info->has_valid_gpu_rendering_duration) -+ if ((frame_info->cpu_time_before_buffer_swap_us != 0 && -+ frame_info->has_valid_gpu_rendering_duration) || -+ frame_clock->ever_got_measurements) - { - int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; -+ int64_t dispatch_time_us = 0, flip_time_us = 0; - -- dispatch_to_swap_us = -- frame_info->cpu_time_before_buffer_swap_us - -- frame_clock->last_dispatch_time_us; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ g_warn_if_reached (); -+ G_GNUC_FALLTHROUGH; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ dispatch_time_us = frame_clock->last_dispatch_time_us; -+ flip_time_us = frame_clock->last_flip_time_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ dispatch_time_us = frame_clock->prev_last_dispatch_time_us; -+ flip_time_us = frame_clock->prev_last_flip_time_us; -+ break; -+ } -+ -+ if (frame_info->cpu_time_before_buffer_swap_us == 0) -+ { -+ /* Cursor-only updates with no "swap" or "flip" */ -+ dispatch_to_swap_us = 0; -+ swap_to_flip_us = 0; -+ } -+ else -+ { -+ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us - -+ dispatch_time_us; -+ swap_to_flip_us = flip_time_us - -+ frame_info->cpu_time_before_buffer_swap_us; -+ } - swap_to_rendering_done_us = - frame_info->gpu_rendering_duration_ns / 1000; -- swap_to_flip_us = -- frame_clock->last_flip_time_us - -- frame_info->cpu_time_before_buffer_swap_us; - - CLUTTER_NOTE (FRAME_TIMINGS, -- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", -+ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", -+ debug_state, - frame_clock->last_dispatch_lateness_us, - dispatch_to_swap_us, - swap_to_rendering_done_us, -@@ -386,7 +433,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + - MAX (swap_to_rendering_done_us, swap_to_flip_us), - frame_clock->shortterm_max_update_duration_us, -- frame_clock->refresh_interval_us); -+ 2 * frame_clock->refresh_interval_us); - - maybe_update_longterm_max_duration_us (frame_clock, frame_info); - -@@ -395,7 +442,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - } - else - { -- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", -+ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs", -+ debug_state, - frame_clock->last_dispatch_lateness_us); - } - -@@ -413,11 +461,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: - g_warn_if_reached (); - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - } - -@@ -435,26 +494,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: - g_warn_if_reached (); - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - } - --static int64_t --clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) -+static gboolean -+clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock, -+ int64_t *max_render_time_us) - { - int64_t refresh_interval_us; -- int64_t max_render_time_us; - - refresh_interval_us = frame_clock->refresh_interval_us; - - if (!frame_clock->ever_got_measurements || - G_UNLIKELY (clutter_paint_debug_flags & - CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) -- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; -+ return FALSE; - - /* Max render time shows how early the frame clock needs to be dispatched - * to make it to the predicted next presentation time. It is an estimate of -@@ -468,15 +538,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) - * - The duration of vertical blank. - * - A constant to account for variations in the above estimates. - */ -- max_render_time_us = -+ *max_render_time_us = - MAX (frame_clock->longterm_max_update_duration_us, - frame_clock->shortterm_max_update_duration_us) + - frame_clock->vblank_duration_us + - clutter_max_render_time_constant_us; - -- max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); -+ *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us); - -- return max_render_time_us; -+ return TRUE; - } - - static void -@@ -491,7 +561,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - int64_t min_render_time_allowed_us; - int64_t max_render_time_allowed_us; - int64_t next_presentation_time_us; -+ int64_t next_smooth_presentation_time_us = 0; - int64_t next_update_time_us; -+ gboolean max_render_time_is_known; - - now_us = g_get_monotonic_time (); - -@@ -511,10 +583,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - - min_render_time_allowed_us = refresh_interval_us / 2; -- max_render_time_allowed_us = -- clutter_frame_clock_compute_max_render_time_us (frame_clock); - -- if (min_render_time_allowed_us > max_render_time_allowed_us) -+ max_render_time_is_known = -+ clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_allowed_us); -+ -+ if (max_render_time_is_known && -+ min_render_time_allowed_us > max_render_time_allowed_us) - min_render_time_allowed_us = max_render_time_allowed_us; - - /* -@@ -535,7 +610,28 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - * - */ - last_presentation_time_us = frame_clock->last_presentation_time_us; -- next_presentation_time_us = last_presentation_time_us + refresh_interval_us; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ refresh_interval_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ 2 * refresh_interval_us; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ next_smooth_presentation_time_us = last_presentation_time_us + -+ 3 * refresh_interval_us; -+ break; -+ } -+ -+ next_presentation_time_us = next_smooth_presentation_time_us; - - /* - * However, the last presentation could have happened more than a frame ago. -@@ -601,7 +697,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - } - -- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us) -+ if (next_presentation_time_us != next_smooth_presentation_time_us) - { - /* There was an idle period since the last presentation, so there seems - * be no constantly updating actor. In this case it's best to start -@@ -613,6 +709,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, - } - else - { -+ /* If the max render time isn't known then using the current value of -+ * next_presentation_time_us is suboptimal. Targeting always one frame -+ * prior to that we'd lose the ability to scale up to triple buffering -+ * on late presentation. But targeting two frames prior we would be -+ * always triple buffering even when not required. -+ * So the algorithm for deciding when to scale up to triple buffering -+ * in the absence of render time measurements is to simply target full -+ * frame rate. If we're keeping up then we'll stay double buffering. If -+ * we're not keeping up then this will switch us to triple buffering. -+ */ -+ if (!max_render_time_is_known) -+ { -+ max_render_time_allowed_us = -+ refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; -+ next_presentation_time_us = -+ last_presentation_time_us + refresh_interval_us; -+ } -+ - while (next_presentation_time_us - min_render_time_allowed_us < now_us) - next_presentation_time_us += refresh_interval_us; - -@@ -644,7 +758,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, - - refresh_interval_us = frame_clock->refresh_interval_us; - -- if (frame_clock->last_presentation_time_us == 0) -+ if (frame_clock->last_presentation_time_us == 0 || -+ !clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_allowed_us)) - { - *out_next_update_time_us = - frame_clock->last_dispatch_time_us ? -@@ -657,9 +773,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, - return; - } - -- max_render_time_allowed_us = -- clutter_frame_clock_compute_max_render_time_us (frame_clock); -- - last_presentation_time_us = frame_clock->last_presentation_time_us; - next_presentation_time_us = last_presentation_time_us + refresh_interval_us; - -@@ -733,8 +846,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) - frame_clock->pending_reschedule_now = TRUE; - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->pending_reschedule_now = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - break; - } - -@@ -753,6 +875,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock) - maybe_reschedule_update (frame_clock); - } - -+static gboolean -+want_triple_buffering (ClutterFrameClock *frame_clock) -+{ -+ switch (triple_buffering_mode) -+ { -+ case TRIPLE_BUFFERING_MODE_NEVER: -+ return FALSE; -+ case TRIPLE_BUFFERING_MODE_AUTO: -+ return frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_FIXED && -+ !(frame_clock->last_flip_hints & -+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); -+ case TRIPLE_BUFFERING_MODE_ALWAYS: -+ return TRUE; -+ } -+ -+ g_assert_not_reached (); -+ return FALSE; -+} -+ - void - clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - { -@@ -770,11 +911,24 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - case CLUTTER_FRAME_CLOCK_STATE_INIT: - case CLUTTER_FRAME_CLOCK_STATE_IDLE: - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: - return; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ if (want_triple_buffering (frame_clock)) -+ { -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; -+ break; -+ } -+ G_GNUC_FALLTHROUGH; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - frame_clock->pending_reschedule = TRUE; - frame_clock->pending_reschedule_now = TRUE; - return; -@@ -803,13 +957,17 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) - - frame_clock->next_update_time_us = next_update_time_us; - g_source_set_ready_time (frame_clock->source, next_update_time_us); -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; - } - - void - clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - { - int64_t next_update_time_us = -1; -+ TripleBufferingMode current_mode = triple_buffering_mode; -+ -+ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && -+ !want_triple_buffering (frame_clock)) -+ current_mode = TRIPLE_BUFFERING_MODE_NEVER; - - if (frame_clock->inhibit_count > 0) - { -@@ -825,12 +983,33 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - return; - case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: - return; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ switch (current_mode) -+ { -+ case TRIPLE_BUFFERING_MODE_NEVER: -+ frame_clock->pending_reschedule = TRUE; -+ return; -+ case TRIPLE_BUFFERING_MODE_AUTO: -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; -+ break; -+ case TRIPLE_BUFFERING_MODE_ALWAYS: -+ next_update_time_us = g_get_monotonic_time (); -+ frame_clock->next_presentation_time_us = 0; -+ frame_clock->is_next_presentation_time_valid = FALSE; -+ frame_clock->state = -+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; -+ return; -+ } -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - frame_clock->pending_reschedule = TRUE; - return; - } -@@ -859,7 +1038,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) - - frame_clock->next_update_time_us = next_update_time_us; - g_source_set_ready_time (frame_clock->source, next_update_time_us); -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; - } - - void -@@ -875,6 +1053,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, - { - case CLUTTER_FRAME_CLOCK_STATE_INIT: - case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: - break; - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: - frame_clock->pending_reschedule = TRUE; -@@ -885,8 +1065,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, - frame_clock->pending_reschedule_now = TRUE; - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->pending_reschedule = TRUE; -+ frame_clock->pending_reschedule_now = TRUE; -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; - break; - } - -@@ -922,7 +1108,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - frame_clock->refresh_interval_us; - - lateness_us = time_us - ideal_dispatch_time_us; -- if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) -+ if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4) - frame_clock->last_dispatch_lateness_us = 0; - else - frame_clock->last_dispatch_lateness_us = lateness_us; -@@ -943,10 +1129,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - } - #endif - -+ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us; - frame_clock->last_dispatch_time_us = time_us; - g_source_set_ready_time (frame_clock->source, -1); - -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; -+ switch (frame_clock->state) -+ { -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ g_warn_if_reached (); -+ return; -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; -+ break; -+ } - - frame_count = frame_clock->frame_count++; - -@@ -977,26 +1180,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, - result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); - COGL_TRACE_END (ClutterFrameClockFrame); - -- switch (frame_clock->state) -+ switch (result) - { -- case CLUTTER_FRAME_CLOCK_STATE_INIT: -- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: -- g_warn_if_reached (); -+ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: - break; -- case CLUTTER_FRAME_CLOCK_STATE_IDLE: -- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -- break; -- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: -- switch (result) -+ case CLUTTER_FRAME_RESULT_IDLE: -+ /* The frame was aborted; nothing to paint/present */ -+ switch (frame_clock->state) - { -- case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: -- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED; -+ case CLUTTER_FRAME_CLOCK_STATE_INIT: -+ case CLUTTER_FRAME_CLOCK_STATE_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: -+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: -+ g_warn_if_reached (); - break; -- case CLUTTER_FRAME_RESULT_IDLE: -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; - maybe_reschedule_update (frame_clock); - break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; -+ maybe_reschedule_update (frame_clock); -+ break; -+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: -+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; -+ maybe_reschedule_update (frame_clock); -+ break; - } - break; - } -@@ -1029,21 +1242,31 @@ frame_clock_source_dispatch (GSource *source, - } - - void --clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, -- int64_t flip_time_us) -+clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, -+ int64_t flip_time_us, -+ ClutterFrameHint hints) - { -+ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; - frame_clock->last_flip_time_us = flip_time_us; -+ frame_clock->last_flip_hints = hints; - } - - GString * - clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) - { -+ int64_t max_render_time_us; - int64_t max_update_duration_us; - GString *string; - -- string = g_string_new (NULL); -- g_string_append_printf (string, "Max render time: %ld µs", -- clutter_frame_clock_compute_max_render_time_us (frame_clock)); -+ string = g_string_new ("Max render time: "); -+ if (!clutter_frame_clock_compute_max_render_time_us (frame_clock, -+ &max_render_time_us)) -+ { -+ g_string_append (string, "unknown"); -+ return string; -+ } -+ -+ g_string_append_printf (string, "%ld µs", max_render_time_us); - - if (frame_clock->got_measurements_last_frame) - g_string_append_printf (string, " ="); -@@ -1210,8 +1433,6 @@ clutter_frame_clock_dispose (GObject *object) - { - ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); - -- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING); -- - if (frame_clock->source) - { - g_signal_emit (frame_clock, signals[DESTROY], 0); -@@ -1235,6 +1456,15 @@ static void - clutter_frame_clock_class_init (ClutterFrameClockClass *klass) - { - GObjectClass *object_class = G_OBJECT_CLASS (klass); -+ const char *mode_str; -+ -+ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING"); -+ if (!g_strcmp0 (mode_str, "never")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER; -+ else if (!g_strcmp0 (mode_str, "auto")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; -+ else if (!g_strcmp0 (mode_str, "always")) -+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS; - - object_class->dispose = clutter_frame_clock_dispose; - -diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h -index a7be5ef31678ffd521884d6bd784af5c74789aab..bfc89bde091660817ff5d1f02e5395668b4c3adb 100644 ---- a/clutter/clutter/clutter-frame-clock.h -+++ b/clutter/clutter/clutter-frame-clock.h -@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult - CLUTTER_FRAME_RESULT_IDLE, - } ClutterFrameResult; - -+typedef enum _ClutterFrameHint -+{ -+ CLUTTER_FRAME_HINT_NONE = 0, -+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0, -+} ClutterFrameHint; -+ - #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ()) - CLUTTER_EXPORT - G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, -@@ -102,7 +108,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, - CLUTTER_EXPORT - float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); - --void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, -- int64_t flip_time_us); -+void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, -+ int64_t flip_time_us, -+ ClutterFrameHint hints); - - GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); -diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h -index ef66b874edfcce434605e4dc733baac9c83ba4d3..ce140560a892c4a55632a48a6f682e8c6a370688 100644 ---- a/clutter/clutter/clutter-frame-private.h -+++ b/clutter/clutter/clutter-frame-private.h -@@ -36,6 +36,7 @@ struct _ClutterFrame - - gboolean has_result; - ClutterFrameResult result; -+ ClutterFrameHint hints; - }; - - CLUTTER_EXPORT -diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c -index 7436f9f182d1de116bc5c8408adec5c164292dc7..53c289b2c5a58105b77861c5362e8e035f6bebbb 100644 ---- a/clutter/clutter/clutter-frame.c -+++ b/clutter/clutter/clutter-frame.c -@@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame, - frame->result = result; - frame->has_result = TRUE; - } -+ -+void -+clutter_frame_set_hint (ClutterFrame *frame, -+ ClutterFrameHint hint) -+{ -+ frame->hints |= hint; -+} -+ -+ClutterFrameHint -+clutter_frame_get_hints (ClutterFrame *frame) -+{ -+ return frame->hints; -+} -diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h -index 34f0770bd7dd630a3d9da5728254b0b5fbcc9e99..c7b3d02acb940bf5712179b576fb5eb7fae13442 100644 ---- a/clutter/clutter/clutter-frame.h -+++ b/clutter/clutter/clutter-frame.h -@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame, - CLUTTER_EXPORT - gboolean clutter_frame_has_result (ClutterFrame *frame); - -+CLUTTER_EXPORT -+void clutter_frame_set_hint (ClutterFrame *frame, -+ ClutterFrameHint hint); -+ -+CLUTTER_EXPORT -+ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame); -+ - G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref) -diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c -index f5188e2acf36125c8c6c58f917a6167423d3c2eb..d53e37785101dda48e673a181a4ffc8802e09784 100644 ---- a/clutter/clutter/clutter-stage-view.c -+++ b/clutter/clutter/clutter-stage-view.c -@@ -898,14 +898,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, - - _clutter_stage_window_redraw_view (stage_window, view, frame); - -- clutter_frame_clock_record_flip_time (frame_clock, -- g_get_monotonic_time ()); -+ clutter_frame_clock_record_flip (frame_clock, -+ g_get_monotonic_time (), -+ clutter_frame_get_hints (frame)); - - clutter_stage_emit_after_paint (stage, view, frame); - - if (_clutter_context_get_show_fps ()) - end_frame_timing_measurement (view); - } -+ else -+ { -+ clutter_frame_clock_record_flip (frame_clock, -+ g_get_monotonic_time (), -+ clutter_frame_get_hints (frame)); -+ } - - _clutter_stage_window_finish_frame (stage_window, view, frame); - +From 6a87d89aff36d62b2a062f437b664cae7ea278bb Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 17 Sep 2021 17:48:20 +0800 +Subject: [PATCH 01/34] cogl/onscreen: Add function + cogl_onscreen_count_pending_frames + +--- + cogl/cogl/cogl-onscreen-private.h | 3 +++ + cogl/cogl/cogl-onscreen.c | 8 ++++++++ + 2 files changed, 11 insertions(+) + diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h -index 959a60533b3fac8fbe2bf791d3228f54e302fb98..86d8ea2d5ff3dbc15f41627475cd8ebf72057bf3 100644 +index 959a60533..785f67aca 100644 --- a/cogl/cogl/cogl-onscreen-private.h +++ b/cogl/cogl/cogl-onscreen-private.h -@@ -78,4 +78,7 @@ COGL_EXPORT CoglFrameInfo * - cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); +@@ -79,3 +79,6 @@ cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); COGL_EXPORT CoglFrameInfo * --cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); -+cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); + cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); + +COGL_EXPORT unsigned int +cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen); diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c -index afb648bcd8110979cc6c0dbe21481822660d2e4e..086be7ed7a9292013842516e9428808080c8eaf6 100644 +index afb648bcd..086be7ed7 100644 --- a/cogl/cogl/cogl-onscreen.c +++ b/cogl/cogl/cogl-onscreen.c @@ -515,6 +515,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen) @@ -820,38 +55,50 @@ index afb648bcd8110979cc6c0dbe21481822660d2e4e..086be7ed7a9292013842516e94288080 CoglFrameClosure * cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen, CoglFrameCallback callback, -diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c -index 7aa24439d73762b82f73b092313582815af45245..727e1a5f30b39b42944eaeeb454226c6402d3a40 100644 ---- a/src/backends/meta-stage-impl.c -+++ b/src/backends/meta-stage-impl.c -@@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window, - { - g_autoptr (GError) error = NULL; - -+ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); -+ - if (meta_stage_impl_scanout_view (stage_impl, - stage_view, - scanout, -diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c -index b15eee14d59a47a8913e59861f033a47bcfa1660..05bc89e83720dd551aa7f2f8a0847a3c8c50a792 100644 ---- a/src/backends/native/meta-kms-impl-device.c -+++ b/src/backends/native/meta-kms-impl-device.c -@@ -1559,9 +1559,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, - meta_kms_update_merge_from (crtc_frame->pending_update, update); - meta_kms_update_free (update); - update = g_steal_pointer (&crtc_frame->pending_update); -- disarm_crtc_frame_deadline_timer (crtc_frame); - } +-- +2.45.0 + + +From 9240d40c44519c15f4bde53e4b82aa706f47f923 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Mon, 12 Feb 2024 17:16:03 +0800 +Subject: [PATCH 02/34] cogl/onscreen: Indent declaration parameters to align + with above/below + +This fixes warnings from check-code-style. +--- + cogl/cogl/cogl-onscreen-private.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h +index 785f67aca..86d8ea2d5 100644 +--- a/cogl/cogl/cogl-onscreen-private.h ++++ b/cogl/cogl/cogl-onscreen-private.h +@@ -78,7 +78,7 @@ COGL_EXPORT CoglFrameInfo * + cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); -+ if (crtc_frame->deadline.armed) -+ disarm_crtc_frame_deadline_timer (crtc_frame); -+ - meta_kms_device_handle_flush (priv->device, latch_crtc); + COGL_EXPORT CoglFrameInfo * +-cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); ++cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); - feedback = do_process (impl_device, latch_crtc, update, flags); + COGL_EXPORT unsigned int + cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen); +-- +2.45.0 + + +From 902dee95d123ddc7c479bd7df83175ec8e0f8d0b Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 20 Apr 2022 18:33:43 +0800 +Subject: [PATCH 03/34] kms: Keep a shutting_down flag + +--- + src/backends/native/meta-kms.c | 9 +++++++++ + src/backends/native/meta-kms.h | 2 ++ + 2 files changed, 11 insertions(+) + diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c -index 795008b210fe97f99252c10248f510bee878c24e..70d1e792c4582d51fbd896ee79fab997b0167de1 100644 +index 795008b21..70d1e792c 100644 --- a/src/backends/native/meta-kms.c +++ b/src/backends/native/meta-kms.c @@ -63,6 +63,8 @@ struct _MetaKms @@ -885,7 +132,7 @@ index 795008b210fe97f99252c10248f510bee878c24e..70d1e792c4582d51fbd896ee79fab997 meta_kms_finalize (GObject *object) { diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h -index 74340140639f14a6913dec269f014e7be42d6db0..f6b19520bec5c13b8685bea139a48b13d0478057 100644 +index 743401406..f6b19520b 100644 --- a/src/backends/native/meta-kms.h +++ b/src/backends/native/meta-kms.h @@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms, @@ -897,42 +144,252 @@ index 74340140639f14a6913dec269f014e7be42d6db0..f6b19520bec5c13b8685bea139a48b13 MetaKms * meta_kms_new (MetaBackend *backend, MetaKmsFlags flags, GError **error); +-- +2.45.0 + + +From 034de98e76be201ebd5a1b62ecc27b8e6d2f61fa Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 26 Oct 2021 18:50:50 +0800 +Subject: [PATCH 04/34] renderer/native: Avoid requeuing the same onscreen for + a power save flip + +This is a case that triple buffering will encounter. We don't want it +to queue the same onscreen multiple times because that would represent +multiple flips occurring simultaneously. + +It's a linear search but the list length is typically only 1 or 2 so +no need for anything fancier yet. +--- + src/backends/native/meta-renderer-native.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c +index aa76d018c..770b5ab75 100644 +--- a/src/backends/native/meta-renderer-native.c ++++ b/src/backends/native/meta-renderer-native.c +@@ -748,6 +748,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na + { + const unsigned int timeout_ms = 100; + ++ if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen)) ++ return; ++ + if (!renderer_native->power_save_page_flip_source_id) + { + renderer_native->power_save_page_flip_source_id = +-- +2.45.0 + + +From 6095010970ae6f77c4f1529baf7acad05293b58e Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Mon, 1 Nov 2021 19:35:34 +0800 +Subject: [PATCH 05/34] renderer/native: Steal the power save flip list before + iterating over it + +Because a single iteration might also grow the list again. +--- + src/backends/native/meta-renderer-native.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c +index 770b5ab75..601fd674a 100644 +--- a/src/backends/native/meta-renderer-native.c ++++ b/src/backends/native/meta-renderer-native.c +@@ -731,12 +731,18 @@ static gboolean + dummy_power_save_page_flip_cb (gpointer user_data) + { + MetaRendererNative *renderer_native = user_data; ++ GList *old_list = ++ g_steal_pointer (&renderer_native->power_save_page_flip_onscreens); + +- g_list_foreach (renderer_native->power_save_page_flip_onscreens, ++ g_list_foreach (old_list, + (GFunc) meta_onscreen_native_dummy_power_save_page_flip, + NULL); +- g_clear_list (&renderer_native->power_save_page_flip_onscreens, ++ g_clear_list (&old_list, + g_object_unref); ++ ++ if (renderer_native->power_save_page_flip_onscreens != NULL) ++ return G_SOURCE_CONTINUE; ++ + renderer_native->power_save_page_flip_source_id = 0; + + return G_SOURCE_REMOVE; +-- +2.45.0 + + +From 581de2f7ee384615545456f00f2e5c0f100c2619 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 10 Dec 2021 16:40:58 +0800 +Subject: [PATCH 06/34] onscreen/native: Log swapbuffers and N-buffering when + MUTTER_DEBUG=kms + +--- + src/backends/native/meta-onscreen-native.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c -index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a0662a3218a 100644 +index 1a31f04a1..2b57731be 100644 --- a/src/backends/native/meta-onscreen-native.c +++ b/src/backends/native/meta-onscreen-native.c -@@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState +@@ -1305,6 +1305,19 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, + "Meta::OnscreenNative::swap_buffers_with_damage()"); - struct { - MetaDrmBufferDumb *current_dumb_fb; -- MetaDrmBufferDumb *dumb_fbs[2]; -+ MetaDrmBufferDumb *dumb_fbs[3]; - } cpu; ++ if (meta_is_topic_enabled (META_DEBUG_KMS)) ++ { ++ unsigned int frames_pending = ++ cogl_onscreen_count_pending_frames (onscreen); ++ ++ meta_topic (META_DEBUG_KMS, ++ "Swap buffers: %u frames pending (%s-buffering)", ++ frames_pending, ++ frames_pending == 1 ? "double" : ++ frames_pending == 2 ? "triple" : ++ "?"); ++ } ++ + secondary_gpu_fb = + update_secondary_gpu_state_pre_swap_buffers (onscreen, + rectangles, +-- +2.45.0 + + +From 17f101b17d367fd1d5c75eaec617101b0ec605e6 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 28 Jul 2021 16:35:56 +0800 +Subject: [PATCH 07/34] onscreen/native: Replace an assertion that double + buffering is the maximum + +Because it soon won't be the maximum. But we do want to verify that the +frame info queue is not empty, to avoid NULL dereferencing and catch logic +errors. +--- + src/backends/native/meta-onscreen-native.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 2b57731be..dbc503ebf 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -207,7 +207,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) - gboolean noted_primary_gpu_copy_ok; -@@ -98,9 +98,13 @@ struct _MetaOnscreenNative - struct { - struct gbm_surface *surface; - MetaDrmBuffer *current_fb; -+ MetaDrmBuffer *posted_fb; - MetaDrmBuffer *next_fb; -+ MetaDrmBuffer *stalled_fb; - CoglScanout *current_scanout; -+ CoglScanout *posted_scanout; - CoglScanout *next_scanout; -+ CoglScanout *stalled_scanout; - } gbm; + info = cogl_onscreen_pop_head_frame_info (onscreen); - #ifdef HAVE_EGL_DEVICE -@@ -125,6 +129,16 @@ struct _MetaOnscreenNative +- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); ++ g_return_if_fail (info); + + _cogl_onscreen_notify_frame_sync (onscreen, info); + _cogl_onscreen_notify_complete (onscreen, info); +-- +2.45.0 + + +From e7754b38c77ff88da16af8361851563ed2e9ea61 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 16 Sep 2021 16:26:25 +0800 +Subject: [PATCH 08/34] onscreen/native: Deduplicate calls to + clutter_frame_set_result + +All paths out of `meta_onscreen_native_swap_buffers_with_damage` from +here onward would set the same `CLUTTER_FRAME_RESULT_PENDING_PRESENTED` +(or terminate with `g_assert_not_reached`). + +Even failed posts set this result because they will do a +`meta_onscreen_native_notify_frame_complete` in +`page_flip_feedback_discarded`. +--- + src/backends/native/meta-onscreen-native.c | 12 +++--------- + 1 file changed, 3 insertions(+), 9 deletions(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index dbc503ebf..06f9e81e8 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -1417,6 +1417,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + #endif + } + ++ clutter_frame_set_result (frame, ++ CLUTTER_FRAME_RESULT_PENDING_PRESENTED); ++ + /* + * If we changed EGL context, cogl will have the wrong idea about what is + * current, making it fail to set it when it needs to. Avoid that by making +@@ -1453,8 +1456,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + { + meta_renderer_native_queue_power_save_page_flip (renderer_native, + onscreen); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + +@@ -1474,8 +1475,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update = meta_frame_native_steal_kms_update (frame_native); + meta_renderer_native_queue_mode_set_update (renderer_native, + kms_update); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + else if (meta_renderer_native_has_pending_mode_set (renderer_native)) +@@ -1489,8 +1488,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + + meta_frame_native_steal_kms_update (frame_native); + meta_renderer_native_post_mode_set_updates (renderer_native); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +@@ -1506,8 +1503,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update); + + meta_renderer_native_post_mode_set_updates (renderer_native); +- clutter_frame_set_result (frame, +- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + break; +@@ -1522,7 +1517,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + kms_update = meta_frame_native_steal_kms_update (frame_native); + meta_kms_device_post_update (kms_device, kms_update, + META_KMS_UPDATE_FLAG_NONE); +- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + } + + gboolean +-- +2.45.0 + + +From 42e23ce307b06eac156ef7877603427862f9f4a2 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 28 Jul 2021 16:29:27 +0800 +Subject: [PATCH 09/34] onscreen/native: Split swap_buffers_with_damage into + two functions + +1. The EGL part: meta_onscreen_native_swap_buffers_with_damage +2. The KMS part: post_latest_swap +--- + src/backends/native/meta-onscreen-native.c | 66 +++++++++++++++++----- + 1 file changed, 53 insertions(+), 13 deletions(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 06f9e81e8..524bc393d 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -125,6 +125,12 @@ struct _MetaOnscreenNative gulong privacy_screen_changed_handler_id; gulong color_space_changed_handler_id; gulong hdr_metadata_changed_handler_id; + -+ gboolean needs_flush; -+ -+ unsigned int swaps_pending; -+ + struct { + int *rectangles; /* 4 x n_rectangles */ + int n_rectangles; @@ -941,20 +398,189 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 }; G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, -@@ -132,44 +146,42 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, +@@ -132,6 +138,9 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, static GQuark blit_source_quark = 0; +static void -+try_post_latest_swap (CoglOnscreen *onscreen); -+ -+static void -+post_finish_frame (MetaOnscreenNative *onscreen_native, -+ MetaKmsUpdate *kms_update); ++post_latest_swap (CoglOnscreen *onscreen); + static gboolean init_secondary_gpu_state (MetaRendererNative *renderer_native, CoglOnscreen *onscreen, +@@ -1279,28 +1288,20 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; +- MetaRenderer *renderer = META_RENDERER (renderer_native); +- MetaBackend *backend = meta_renderer_get_backend (renderer); +- MetaMonitorManager *monitor_manager = +- meta_backend_get_monitor_manager (backend); + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + MetaGpuKms *render_gpu = onscreen_native->render_gpu; + MetaDeviceFile *render_device_file; + ClutterFrame *frame = user_data; +- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); +- MetaKmsUpdate *kms_update; + CoglOnscreenClass *parent_class; + gboolean create_timestamp_query = TRUE; + gboolean egl_context_changed = FALSE; +- MetaPowerSave power_save_mode; + g_autoptr (GError) error = NULL; + MetaDrmBufferFlags buffer_flags; + MetaDrmBufferGbm *buffer_gbm; + g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; + g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; +- MetaKmsCrtc *kms_crtc; +- MetaKmsDevice *kms_device; ++ size_t rectangles_size; + + COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, + "Meta::OnscreenNative::swap_buffers_with_damage()"); +@@ -1429,12 +1430,47 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + if (egl_context_changed) + _cogl_winsys_egl_ensure_current (cogl_display); + +- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); +- kms_device = meta_kms_crtc_get_device (kms_crtc); ++ rectangles_size = n_rectangles * 4 * sizeof (int); ++ onscreen_native->next_post.rectangles = ++ g_realloc (onscreen_native->next_post.rectangles, rectangles_size); ++ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size); ++ onscreen_native->next_post.n_rectangles = n_rectangles; ++ ++ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); ++ onscreen_native->next_post.frame = clutter_frame_ref (frame); ++ ++ post_latest_swap (onscreen); ++} ++ ++static void ++post_latest_swap (CoglOnscreen *onscreen) ++{ ++ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); ++ CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); ++ CoglRenderer *cogl_renderer = cogl_context->display->renderer; ++ CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; ++ MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; ++ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; ++ MetaRenderer *renderer = META_RENDERER (renderer_native); ++ MetaBackend *backend = meta_renderer_get_backend (renderer); ++ MetaMonitorManager *monitor_manager = ++ meta_backend_get_monitor_manager (backend); ++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); ++ MetaPowerSave power_save_mode; ++ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); ++ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); ++ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); ++ MetaKmsUpdate *kms_update; ++ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; ++ g_autoptr (ClutterFrame) frame = ++ g_steal_pointer (&onscreen_native->next_post.frame); ++ MetaFrameNative *frame_native; + + power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); + if (power_save_mode == META_POWER_SAVE_ON) + { ++ frame_native = meta_frame_native_from_frame (frame); ++ + kms_update = meta_frame_native_ensure_kms_update (frame_native, + kms_device); + meta_kms_update_add_result_listener (kms_update, +@@ -1449,8 +1485,8 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + onscreen_native->crtc, + kms_update, + META_KMS_ASSIGN_PLANE_FLAG_NONE, +- rectangles, +- n_rectangles); ++ onscreen_native->next_post.rectangles, ++ onscreen_native->next_post.n_rectangles); + } + else + { +@@ -2872,6 +2908,10 @@ meta_onscreen_native_dispose (GObject *object) + + g_clear_object (&onscreen_native->output); + g_clear_object (&onscreen_native->crtc); ++ ++ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free); ++ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); ++ onscreen_native->next_post.n_rectangles = 0; + } + + static void +-- +2.45.0 + + +From db2680938786f83147215c6ac2ceddc3ea805ab9 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 28 Oct 2021 17:24:11 +0800 +Subject: [PATCH 10/34] onscreen/native: Add a missing frame notification + +In the unlikely event that swap_buffers_with_damage drops the previous swap +it was forgetting to notify about the discarded frame. That could lead to +frame clock freezes. +--- + src/backends/native/meta-onscreen-native.c | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 524bc393d..eaa00d45a 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -1394,6 +1394,15 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + { + case META_RENDERER_NATIVE_MODE_GBM: + g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); ++ if (onscreen_native->gbm.next_fb != NULL) ++ { ++ CoglFrameInfo *frame_info; ++ ++ frame_info = cogl_onscreen_peek_head_frame_info (onscreen); ++ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; ++ meta_onscreen_native_notify_frame_complete (onscreen); ++ } ++ + if (onscreen_native->secondary_gpu_state) + g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); + else +-- +2.45.0 + + +From dfa3cd632df4e2856855d7b8ae8ee557466e24bf Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 5 Dec 2023 17:50:44 +0800 +Subject: [PATCH 11/34] onscreen/native: Insert a 'posted' frame between 'next' + and 'current' + +This will allow us to keep track of up to two buffers that have been +swapped but not yet scanning out, for triple buffering. + +This commit replaces mutter!1968. +--- + src/backends/native/meta-onscreen-native.c | 58 ++++++++++++---------- + 1 file changed, 31 insertions(+), 27 deletions(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index eaa00d45a..2617b1606 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -98,8 +98,10 @@ struct _MetaOnscreenNative + struct { + struct gbm_surface *surface; + MetaDrmBuffer *current_fb; ++ MetaDrmBuffer *posted_fb; + MetaDrmBuffer *next_fb; + CoglScanout *current_scanout; ++ CoglScanout *posted_scanout; + CoglScanout *next_scanout; + } gbm; + +@@ -146,39 +148,30 @@ init_secondary_gpu_state (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, GError **error); -static void @@ -1002,16 +628,139 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 } static void -@@ -207,7 +219,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) +@@ -359,7 +352,7 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - info = cogl_onscreen_pop_head_frame_info (onscreen); + meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ meta_onscreen_native_clear_posted_fb (onscreen); + } -- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); -+ g_return_if_fail (info); + static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { +@@ -530,13 +523,21 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, + switch (renderer_gpu_data->mode) + { + case META_RENDERER_NATIVE_MODE_GBM: +- buffer = onscreen_native->gbm.next_fb; ++ g_set_object (&onscreen_native->gbm.posted_fb, ++ onscreen_native->gbm.next_fb); ++ g_clear_object (&onscreen_native->gbm.next_fb); ++ ++ buffer = onscreen_native->gbm.posted_fb; ++ ++ g_set_object (&onscreen_native->gbm.posted_scanout, ++ onscreen_native->gbm.next_scanout); ++ g_clear_object (&onscreen_native->gbm.next_scanout); - _cogl_onscreen_notify_frame_sync (onscreen, info); - _cogl_onscreen_notify_complete (onscreen, info); -@@ -243,6 +255,7 @@ notify_view_crtc_presented (MetaRendererView *view, +- if (onscreen_native->gbm.next_scanout) ++ if (onscreen_native->gbm.posted_scanout) + { +- cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, ++ cogl_scanout_get_src_rect (onscreen_native->gbm.posted_scanout, + &src_rect); +- cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout, ++ cogl_scanout_get_dst_rect (onscreen_native->gbm.posted_scanout, + &dst_rect); + } + else +@@ -1267,7 +1268,7 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + + meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ meta_onscreen_native_clear_posted_fb (onscreen); + } + + static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { +@@ -1632,7 +1633,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, + + g_warning ("Direct scanout page flip failed: %s", error->message); + +- cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, ++ cogl_scanout_notify_failed (onscreen_native->gbm.posted_scanout, + onscreen); + clutter_stage_view_add_redraw_clip (view, NULL); + clutter_stage_view_schedule_update_now (view); +@@ -1642,7 +1643,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + + meta_onscreen_native_notify_frame_complete (onscreen); +- meta_onscreen_native_clear_next_fb (onscreen); ++ meta_onscreen_native_clear_posted_fb (onscreen); + } + + static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { +@@ -2882,8 +2883,11 @@ meta_onscreen_native_dispose (GObject *object) + { + case META_RENDERER_NATIVE_MODE_GBM: + g_clear_object (&onscreen_native->gbm.next_fb); ++ g_clear_object (&onscreen_native->gbm.posted_fb); ++ g_clear_object (&onscreen_native->gbm.current_fb); + g_clear_object (&onscreen_native->gbm.next_scanout); +- free_current_bo (onscreen); ++ g_clear_object (&onscreen_native->gbm.posted_scanout); ++ g_clear_object (&onscreen_native->gbm.current_scanout); + break; + case META_RENDERER_NATIVE_MODE_SURFACELESS: + g_assert_not_reached (); +-- +2.45.0 + + +From 4c18c898c9bb06855da10b451dae960939cebda9 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 17 Sep 2021 17:59:28 +0800 +Subject: [PATCH 12/34] onscreen/native: Defer posting if there's already a + post in progress + +And when the number of pending posts decreases we know it's safe to submit +a new one. Since KMS generally only supports one outstanding post right now, +"decreases" means equal to zero. +--- + src/backends/native/meta-onscreen-native.c | 192 ++++++++++++++++++--- + 1 file changed, 164 insertions(+), 28 deletions(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 2617b1606..d57aad0b2 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -100,9 +100,11 @@ struct _MetaOnscreenNative + MetaDrmBuffer *current_fb; + MetaDrmBuffer *posted_fb; + MetaDrmBuffer *next_fb; ++ MetaDrmBuffer *stalled_fb; + CoglScanout *current_scanout; + CoglScanout *posted_scanout; + CoglScanout *next_scanout; ++ CoglScanout *stalled_scanout; + } gbm; + + #ifdef HAVE_EGL_DEVICE +@@ -128,6 +130,10 @@ struct _MetaOnscreenNative + gulong color_space_changed_handler_id; + gulong hdr_metadata_changed_handler_id; + ++ gboolean needs_flush; ++ ++ unsigned int swaps_pending; ++ + struct { + int *rectangles; /* 4 x n_rectangles */ + int n_rectangles; +@@ -141,7 +147,11 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, + static GQuark blit_source_quark = 0; + + static void +-post_latest_swap (CoglOnscreen *onscreen); ++try_post_latest_swap (CoglOnscreen *onscreen); ++ ++static void ++post_finish_frame (MetaOnscreenNative *onscreen_native, ++ MetaKmsUpdate *kms_update); + + static gboolean + init_secondary_gpu_state (MetaRendererNative *renderer_native, +@@ -245,6 +255,7 @@ notify_view_crtc_presented (MetaRendererView *view, meta_onscreen_native_notify_frame_complete (onscreen); meta_onscreen_native_swap_drm_fb (onscreen); @@ -1019,7 +768,7 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 } static void -@@ -292,15 +305,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, +@@ -294,15 +305,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); @@ -1036,17 +785,15 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 } static void -@@ -350,7 +361,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; +@@ -353,6 +362,7 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); -+ meta_onscreen_native_clear_posted_fb (onscreen); + meta_onscreen_native_clear_posted_fb (onscreen); + try_post_latest_swap (onscreen); } static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { -@@ -411,18 +423,41 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data, +@@ -413,18 +423,41 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data, } #endif /* HAVE_EGL_DEVICE */ @@ -1091,62 +838,13 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 static void apply_transform (MetaCrtcKms *crtc_kms, MetaKmsPlaneAssignment *kms_plane_assignment, -@@ -521,13 +556,21 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, - switch (renderer_gpu_data->mode) - { - case META_RENDERER_NATIVE_MODE_GBM: -- buffer = onscreen_native->gbm.next_fb; -+ g_set_object (&onscreen_native->gbm.posted_fb, -+ onscreen_native->gbm.next_fb); -+ g_clear_object (&onscreen_native->gbm.next_fb); -+ -+ buffer = onscreen_native->gbm.posted_fb; - -- if (onscreen_native->gbm.next_scanout) -+ g_set_object (&onscreen_native->gbm.posted_scanout, -+ onscreen_native->gbm.next_scanout); -+ g_clear_object (&onscreen_native->gbm.next_scanout); -+ -+ if (onscreen_native->gbm.posted_scanout) - { -- cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, -+ cogl_scanout_get_src_rect (onscreen_native->gbm.posted_scanout, - &src_rect); -- cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout, -+ cogl_scanout_get_dst_rect (onscreen_native->gbm.posted_scanout, - &dst_rect); - } - else -@@ -918,12 +961,17 @@ static MetaDrmBufferDumb * - secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) - { - MetaDrmBufferDumb *current_dumb_fb; -+ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); -+ int i; - - current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; -- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) -- return secondary_gpu_state->cpu.dumb_fbs[1]; -- else -- return secondary_gpu_state->cpu.dumb_fbs[0]; -+ for (i = 0; i < n_dumb_fbs; i++) -+ { -+ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i]) -+ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs]; -+ } -+ -+ return secondary_gpu_state->cpu.dumb_fbs[0]; - } - - static MetaDrmBuffer * -@@ -1255,10 +1303,17 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, +@@ -1265,9 +1298,16 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, g_warning ("Page flip failed: %s", error->message); frame_info = cogl_onscreen_peek_head_frame_info (onscreen); - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); + /* After resuming from suspend, drop_stalled_swap might have done this + * already and emptied the frame_info queue. + */ @@ -1156,64 +854,21 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 + meta_onscreen_native_notify_frame_complete (onscreen); + } + -+ meta_onscreen_native_clear_posted_fb (onscreen); + meta_onscreen_native_clear_posted_fb (onscreen); } - static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { -@@ -1279,32 +1334,37 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; - MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; - MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; -- MetaRenderer *renderer = META_RENDERER (renderer_native); -- MetaBackend *backend = meta_renderer_get_backend (renderer); -- MetaMonitorManager *monitor_manager = -- meta_backend_get_monitor_manager (backend); - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; - MetaGpuKms *render_gpu = onscreen_native->render_gpu; - MetaDeviceFile *render_device_file; - ClutterFrame *frame = user_data; -- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); -- MetaKmsUpdate *kms_update; - CoglOnscreenClass *parent_class; - gboolean create_timestamp_query = TRUE; - gboolean egl_context_changed = FALSE; -- MetaPowerSave power_save_mode; - g_autoptr (GError) error = NULL; - MetaDrmBufferFlags buffer_flags; - MetaDrmBufferGbm *buffer_gbm; - g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; - g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; -- MetaKmsCrtc *kms_crtc; -- MetaKmsDevice *kms_device; -+ size_t rectangles_size; - - COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, - "Meta::OnscreenNative::swap_buffers_with_damage()"); - -+ if (meta_is_topic_enabled (META_DEBUG_KMS)) -+ { -+ unsigned int frames_pending = -+ cogl_onscreen_count_pending_frames (onscreen); -+ -+ meta_topic (META_DEBUG_KMS, -+ "Swap buffers: %u frames pending (%s-buffering)", -+ frames_pending, -+ frames_pending == 1 ? "double" : -+ frames_pending == 2 ? "triple" : -+ "?"); -+ } -+ - secondary_gpu_fb = - update_secondary_gpu_state_pre_swap_buffers (onscreen, - rectangles, -@@ -1379,7 +1439,17 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, +@@ -1394,14 +1434,15 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, switch (renderer_gpu_data->mode) { case META_RENDERER_NATIVE_MODE_GBM: - g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); -+ if (onscreen_native->gbm.next_fb != NULL) -+ { + if (onscreen_native->gbm.next_fb != NULL) + { +- CoglFrameInfo *frame_info; +- +- frame_info = cogl_onscreen_peek_head_frame_info (onscreen); +- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; +- meta_onscreen_native_notify_frame_complete (onscreen); + g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL); + drop_stalled_swap (onscreen); + g_assert (onscreen_native->gbm.stalled_fb == NULL); @@ -1221,70 +876,36 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 + g_steal_pointer (&onscreen_native->gbm.next_fb); + onscreen_native->gbm.stalled_scanout = + g_steal_pointer (&onscreen_native->gbm.next_scanout); -+ } -+ - if (onscreen_native->secondary_gpu_state) - g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); - else -@@ -1404,6 +1474,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - #endif - } + } -+ clutter_frame_set_result (frame, -+ CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+ - /* - * If we changed EGL context, cogl will have the wrong idea about what is - * current, making it fail to set it when it needs to. Avoid that by making -@@ -1413,12 +1486,78 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - if (egl_context_changed) - _cogl_winsys_egl_ensure_current (cogl_display); + if (onscreen_native->secondary_gpu_state) +@@ -1449,11 +1490,12 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, + g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); + onscreen_native->next_post.frame = clutter_frame_ref (frame); -- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); -- kms_device = meta_kms_crtc_get_device (kms_crtc); -+ rectangles_size = n_rectangles * 4 * sizeof (int); -+ onscreen_native->next_post.rectangles = -+ g_realloc (onscreen_native->next_post.rectangles, rectangles_size); -+ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size); -+ onscreen_native->next_post.n_rectangles = n_rectangles; -+ -+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); -+ onscreen_native->next_post.frame = clutter_frame_ref (frame); -+ +- post_latest_swap (onscreen); + onscreen_native->swaps_pending++; + try_post_latest_swap (onscreen); -+} -+ -+static void + } + + static void +-post_latest_swap (CoglOnscreen *onscreen) +try_post_latest_swap (CoglOnscreen *onscreen) -+{ -+ CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); -+ CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); -+ CoglRenderer *cogl_renderer = cogl_context->display->renderer; -+ CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; -+ MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; -+ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; -+ MetaRenderer *renderer = META_RENDERER (renderer_native); -+ MetaBackend *backend = meta_renderer_get_backend (renderer); -+ MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); -+ MetaKms *kms = meta_backend_native_get_kms (backend_native); -+ MetaMonitorManager *monitor_manager = -+ meta_backend_get_monitor_manager (backend); -+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -+ MetaPowerSave power_save_mode; -+ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); -+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); -+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); -+ MetaKmsUpdate *kms_update; -+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + { + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); +@@ -1472,15 +1514,41 @@ post_latest_swap (CoglOnscreen *onscreen) + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); + MetaKmsUpdate *kms_update; + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; +- g_autoptr (ClutterFrame) frame = +- g_steal_pointer (&onscreen_native->next_post.frame); + g_autoptr (ClutterFrame) frame = NULL; -+ MetaFrameNative *frame_native; -+ -+ if (onscreen_native->next_post.frame == NULL || -+ onscreen_native->view == NULL || -+ meta_kms_is_shutting_down (kms)) -+ return; + MetaFrameNative *frame_native; ++ if (onscreen_native->next_post.frame == NULL) ++ return; ++ power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); if (power_save_mode == META_POWER_SAVE_ON) { @@ -1298,8 +919,8 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 + return; /* wait for the next frame notification and then try again */ + + frame = g_steal_pointer (&onscreen_native->next_post.frame); -+ frame_native = meta_frame_native_from_frame (frame); -+ + frame_native = meta_frame_native_from_frame (frame); + + if (onscreen_native->swaps_pending == 0) + { + if (frame_native) @@ -1317,78 +938,7 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device); meta_kms_update_add_result_listener (kms_update, -@@ -1433,15 +1572,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - onscreen_native->crtc, - kms_update, - META_KMS_ASSIGN_PLANE_FLAG_NONE, -- rectangles, -- n_rectangles); -+ onscreen_native->next_post.rectangles, -+ onscreen_native->next_post.n_rectangles); - } - else - { - meta_renderer_native_queue_power_save_page_flip (renderer_native, - onscreen); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - -@@ -1461,8 +1598,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update = meta_frame_native_steal_kms_update (frame_native); - meta_renderer_native_queue_mode_set_update (renderer_native, - kms_update); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - else if (meta_renderer_native_has_pending_mode_set (renderer_native)) -@@ -1476,8 +1611,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - - meta_frame_native_steal_kms_update (frame_native); - meta_renderer_native_post_mode_set_updates (renderer_native); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - break; -@@ -1493,8 +1626,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update); - - meta_renderer_native_post_mode_set_updates (renderer_native); -- clutter_frame_set_result (frame, -- CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - return; - } - break; -@@ -1509,7 +1640,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, - kms_update = meta_frame_native_steal_kms_update (frame_native); - meta_kms_device_post_update (kms_device, kms_update, - META_KMS_UPDATE_FLAG_NONE); -- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); - } - - gboolean -@@ -1580,7 +1710,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, - - g_warning ("Direct scanout page flip failed: %s", error->message); - -- cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, -+ cogl_scanout_notify_failed (onscreen_native->gbm.posted_scanout, - onscreen); - clutter_stage_view_add_redraw_clip (view, NULL); - clutter_stage_view_schedule_update_now (view); -@@ -1590,7 +1720,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - - meta_onscreen_native_notify_frame_complete (onscreen); -- meta_onscreen_native_clear_next_fb (onscreen); -+ meta_onscreen_native_clear_posted_fb (onscreen); - } - - static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { -@@ -1642,6 +1772,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, +@@ -1695,6 +1763,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, return FALSE; } @@ -1407,19 +957,7 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, render_gpu); -@@ -1757,11 +1899,7 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen, - ClutterFrame *frame) - { - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); -- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); -- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); - -- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), -- kms_crtc); - maybe_update_frame_sync (onscreen_native, frame); - } - -@@ -1877,22 +2015,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, +@@ -1930,22 +2010,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); MetaKmsUpdate *kms_update; @@ -1507,13 +1045,90 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 meta_kms_update_add_result_listener (kms_update, &finish_frame_result_listener_vtable, NULL, -@@ -1915,7 +2110,19 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, +@@ -1968,7 +2105,6 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, meta_kms_update_set_flushing (kms_update, kms_crtc); meta_kms_device_post_update (kms_device, kms_update, META_KMS_UPDATE_FLAG_NONE); - clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); -+} + } + + static gboolean +-- +2.45.0 + + +From 24b5694ab79f028933678b8148d43e6a03edb16f Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 9 Dec 2022 14:22:31 +0800 +Subject: [PATCH 13/34] onscreen/native: Increase secondary GPU dumb_fbs from 2 + to 3 + +So that they don't get overwritten prematurely during triple buffering +causing tearing. + +https://launchpad.net/bugs/1999216 +--- + src/backends/native/meta-onscreen-native.c | 15 ++++++++++----- + 1 file changed, 10 insertions(+), 5 deletions(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index d57aad0b2..a8e8dcfb7 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -76,7 +76,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState + + struct { + MetaDrmBufferDumb *current_dumb_fb; +- MetaDrmBufferDumb *dumb_fbs[2]; ++ MetaDrmBufferDumb *dumb_fbs[3]; + } cpu; + + gboolean noted_primary_gpu_copy_ok; +@@ -961,12 +961,17 @@ static MetaDrmBufferDumb * + secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) + { + MetaDrmBufferDumb *current_dumb_fb; ++ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); ++ int i; + + current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; +- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) +- return secondary_gpu_state->cpu.dumb_fbs[1]; +- else +- return secondary_gpu_state->cpu.dumb_fbs[0]; ++ for (i = 0; i < n_dumb_fbs; i++) ++ { ++ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i]) ++ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs]; ++ } + ++ return secondary_gpu_state->cpu.dumb_fbs[0]; + } + + static MetaDrmBuffer * +-- +2.45.0 + + +From a63610a3175bf5cc2202930eef8d1986d2660c7c Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 4 Nov 2021 16:09:26 +0800 +Subject: [PATCH 14/34] onscreen/native: Add function + meta_onscreen_native_discard_pending_swaps + +--- + src/backends/native/meta-onscreen-native.c | 13 +++++++++++++ + src/backends/native/meta-onscreen-native.h | 2 ++ + 2 files changed, 15 insertions(+) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index a8e8dcfb7..0c608237c 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -2112,6 +2112,19 @@ post_finish_frame (MetaOnscreenNative *onscreen_native, + META_KMS_UPDATE_FLAG_NONE); + } + +void +meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen) +{ @@ -1525,35 +1140,13 @@ index 1a31f04a164d9acf05523555cb2f515b23a526cc..9836663d0c7f481d82c8715f1d856a06 + g_clear_object (&onscreen_native->gbm.stalled_scanout); + g_clear_object (&onscreen_native->gbm.next_fb); + g_clear_object (&onscreen_native->gbm.next_scanout); - } - - static gboolean -@@ -2830,8 +3037,11 @@ meta_onscreen_native_dispose (GObject *object) - { - case META_RENDERER_NATIVE_MODE_GBM: - g_clear_object (&onscreen_native->gbm.next_fb); -+ g_clear_object (&onscreen_native->gbm.posted_fb); -+ g_clear_object (&onscreen_native->gbm.current_fb); - g_clear_object (&onscreen_native->gbm.next_scanout); -- free_current_bo (onscreen); -+ g_clear_object (&onscreen_native->gbm.posted_scanout); -+ g_clear_object (&onscreen_native->gbm.current_scanout); - break; - case META_RENDERER_NATIVE_MODE_SURFACELESS: - g_assert_not_reached (); -@@ -2865,6 +3075,10 @@ meta_onscreen_native_dispose (GObject *object) - - g_clear_object (&onscreen_native->output); - g_clear_object (&onscreen_native->crtc); ++} + -+ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free); -+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); -+ onscreen_native->next_post.n_rectangles = 0; - } - - static void + static gboolean + should_surface_be_sharable (CoglOnscreen *onscreen) + { diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h -index 0e1193325a958fadc28e1177d61171fe459f284d..e30357d19d1fd4544026f0f7ef6dba6221d571a9 100644 +index 0e1193325..e30357d19 100644 --- a/src/backends/native/meta-onscreen-native.h +++ b/src/backends/native/meta-onscreen-native.h @@ -48,6 +48,8 @@ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen); @@ -1565,42 +1158,80 @@ index 0e1193325a958fadc28e1177d61171fe459f284d..e30357d19d1fd4544026f0f7ef6dba62 void meta_onscreen_native_set_view (CoglOnscreen *onscreen, MetaRendererView *view); +-- +2.45.0 + + +From 99d1a2d1be42502ab419f947e1aac483c8950000 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 11 May 2022 16:00:32 +0800 +Subject: [PATCH 15/34] onscreen/native: Skip try_post_latest_swap if shutting + down + +Otherwise we could get: + + meta_kms_prepare_shutdown -> + flush_callbacks -> + ... -> + try_post_latest_swap -> + post and queue more callbacks + +So later in shutdown those callbacks would trigger an assertion failure +in meta_kms_impl_device_atomic_finalize: + + g_hash_table_size (impl_device_atomic->page_flip_datas) == 0 +--- + src/backends/native/meta-onscreen-native.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 0c608237c..65e48f2ea 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -1510,6 +1510,8 @@ try_post_latest_swap (CoglOnscreen *onscreen) + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaRenderer *renderer = META_RENDERER (renderer_native); + MetaBackend *backend = meta_renderer_get_backend (renderer); ++ MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); ++ MetaKms *kms = meta_backend_native_get_kms (backend_native); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); +@@ -1522,7 +1524,8 @@ try_post_latest_swap (CoglOnscreen *onscreen) + g_autoptr (ClutterFrame) frame = NULL; + MetaFrameNative *frame_native; + +- if (onscreen_native->next_post.frame == NULL) ++ if (onscreen_native->next_post.frame == NULL || ++ meta_kms_is_shutting_down (kms)) + return; + + power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); +-- +2.45.0 + + +From 0daf4a48f8cec7cc672249b28652bf0267bda9ee Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 4 Nov 2021 16:09:52 +0800 +Subject: [PATCH 16/34] renderer/native: Discard pending swaps when rebuilding + views + +It's analogous to discard_pending_page_flips but represents swaps that +might become flips after the next frame notification callbacks, thanks +to triple buffering. Since the views are being rebuilt and their onscreens +are about to be destroyed, turning those swaps into more flips/posts would +just lead to unexpected behaviour (like trying to flip on a half-destroyed +inactive CRTC). +--- + src/backends/native/meta-renderer-native.c | 21 +++++++++++++++++++++ + 1 file changed, 21 insertions(+) + diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c -index aa76d018ca29b56271d34d81bf39995e14093884..3c22b4e86a7cff8ed7bf378621c9ec63c6e79e5f 100644 +index 601fd674a..3c22b4e86 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c -@@ -731,12 +731,18 @@ static gboolean - dummy_power_save_page_flip_cb (gpointer user_data) - { - MetaRendererNative *renderer_native = user_data; -+ GList *old_list = -+ g_steal_pointer (&renderer_native->power_save_page_flip_onscreens); - -- g_list_foreach (renderer_native->power_save_page_flip_onscreens, -+ g_list_foreach (old_list, - (GFunc) meta_onscreen_native_dummy_power_save_page_flip, - NULL); -- g_clear_list (&renderer_native->power_save_page_flip_onscreens, -+ g_clear_list (&old_list, - g_object_unref); -+ -+ if (renderer_native->power_save_page_flip_onscreens != NULL) -+ return G_SOURCE_CONTINUE; -+ - renderer_native->power_save_page_flip_source_id = 0; - - return G_SOURCE_REMOVE; -@@ -748,6 +754,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na - { - const unsigned int timeout_ms = 100; - -+ if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen)) -+ return; -+ - if (!renderer_native->power_save_page_flip_source_id) - { - renderer_native->power_save_page_flip_source_id = -@@ -1529,6 +1538,26 @@ detach_onscreens (MetaRenderer *renderer) +@@ -1538,6 +1538,26 @@ detach_onscreens (MetaRenderer *renderer) } } @@ -1627,7 +1258,7 @@ index aa76d018ca29b56271d34d81bf39995e14093884..3c22b4e86a7cff8ed7bf378621c9ec63 static void meta_renderer_native_rebuild_views (MetaRenderer *renderer) { -@@ -1539,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer) +@@ -1548,6 +1568,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer) MetaRendererClass *parent_renderer_class = META_RENDERER_CLASS (meta_renderer_native_parent_class); @@ -1635,8 +1266,1075 @@ index aa76d018ca29b56271d34d81bf39995e14093884..3c22b4e86a7cff8ed7bf378621c9ec63 meta_kms_discard_pending_page_flips (kms); g_hash_table_remove_all (renderer_native->mode_set_updates); +-- +2.45.0 + + +From fab0d3d57ed2721b32dcc2e282b9f5f6cce3857d Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 7 Dec 2023 16:28:20 +0800 +Subject: [PATCH 17/34] clutter/frame-clock: Reuse existing idle period + detection + +While it was correct for double buffering, the test was wrong for +triple buffering and would lead to accidentally staying in triple +buffering for too long. + +Fixes: 8f27ebf87eee6057992a90560d4118ab7bdf138d +--- + clutter/clutter/clutter-frame-clock.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 93e4c9329..1b212ee3f 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -491,6 +491,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + int64_t min_render_time_allowed_us; + int64_t max_render_time_allowed_us; + int64_t next_presentation_time_us; ++ int64_t next_smooth_presentation_time_us; + int64_t next_update_time_us; + + now_us = g_get_monotonic_time (); +@@ -535,7 +536,8 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + * + */ + last_presentation_time_us = frame_clock->last_presentation_time_us; +- next_presentation_time_us = last_presentation_time_us + refresh_interval_us; ++ next_smooth_presentation_time_us = last_presentation_time_us + refresh_interval_us; ++ next_presentation_time_us = next_smooth_presentation_time_us; + + /* + * However, the last presentation could have happened more than a frame ago. +@@ -601,7 +603,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + } + +- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us) ++ if (next_presentation_time_us != next_smooth_presentation_time_us) + { + /* There was an idle period since the last presentation, so there seems + * be no constantly updating actor. In this case it's best to start +-- +2.45.0 + + +From e106557ea916ca8400f2059c7e5a257ce5d2ceac Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 10 Aug 2021 17:46:49 +0800 +Subject: [PATCH 18/34] clutter/frame-clock: Lower the threshold for disabling + error diffusion + +Error diffusion was introduced in 0555a5bbc15 for Nvidia where last +presentation time is always unknown (zero). Dispatch times would drift +apart always being a fraction of a frame late, and accumulated to cause +periodic frame skips. So error diffusion corrected that precisely and +avoided the skips. + +That works great with double buffering but less great with triple +buffering. It's certainly still needed with triple buffering but +correcting for a lateness of many milliseconds isn't a good idea. That's +because a dispatch being that late is not due to main loop jitter but due +to Nvidia's swap buffers blocking when the queue is full. So scheduling +the next frame even earlier using last_dispatch_lateness_us would just +perpetuate the problem of swap buffers blocking for too long. + +So now we lower the threshold of when error diffusion gets disabled. It's +still high enough to fix the original smoothness problem it was for, but +now low enough to detect Nvidia's occasionally blocking swaps and backs +off in that case. + +Since the average duration of a blocking swap is half a frame interval +and we want to distinguish between that and sub-millisecond jitter, the +logical threshold is halfway again: refresh_interval_us/4. +--- + clutter/clutter/clutter-frame-clock.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 1b212ee3f..48f07e101 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -924,7 +924,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + frame_clock->refresh_interval_us; + + lateness_us = time_us - ideal_dispatch_time_us; +- if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) ++ if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4) + frame_clock->last_dispatch_lateness_us = 0; + else + frame_clock->last_dispatch_lateness_us = lateness_us; +-- +2.45.0 + + +From 776e619e0311eebd9ff87c5d303512b91df42b20 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 24 Jul 2020 14:13:11 +0800 +Subject: [PATCH 19/34] clutter/frame-clock: Merge states DISPATCHING and + PENDING_PRESENTED + +Chronologically they already overlap in time as presentation may +complete in the middle of the dispatch function, otherwise they are +contiguous in time. And most switch statements treated the two states +the same already so they're easy to merge into a single `DISPATCHED` +state. + +Having fewer states now will make life easier when we add more states +later. +--- + clutter/clutter/clutter-frame-clock.c | 31 ++++++++++----------------- + 1 file changed, 11 insertions(+), 20 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 48f07e101..384537e41 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -70,8 +70,7 @@ typedef enum _ClutterFrameClockState + CLUTTER_FRAME_CLOCK_STATE_IDLE, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, +- CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, +- CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED, + } ClutterFrameClockState; + + struct _ClutterFrameClock +@@ -413,8 +412,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; +@@ -435,8 +433,7 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; +@@ -735,8 +732,7 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + break; + } + +@@ -775,8 +771,7 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = TRUE; + return; +@@ -831,8 +826,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + frame_clock->pending_reschedule = TRUE; + return; + } +@@ -887,8 +881,7 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + break; + } + +@@ -948,7 +941,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + frame_clock->last_dispatch_time_us = time_us; + g_source_set_ready_time (frame_clock->source, -1); + +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED; + + frame_count = frame_clock->frame_count++; + +@@ -982,20 +975,20 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: +- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + g_warn_if_reached (); + break; + case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ /* Presentation completed synchronously in the above listener */ + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: + switch (result) + { + case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED; + break; + case CLUTTER_FRAME_RESULT_IDLE: ++ /* The frame was aborted; nothing to paint/present */ + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; +@@ -1212,8 +1205,6 @@ clutter_frame_clock_dispose (GObject *object) + { + ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); + +- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING); +- + if (frame_clock->source) + { + g_signal_emit (frame_clock, signals[DESTROY], 0); +-- +2.45.0 + + +From f28ce8aa1cc9d5dba8611cefb570f4ca1dec43d6 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Mon, 13 Dec 2021 17:03:44 +0800 +Subject: [PATCH 20/34] clutter/frame-clock: Clamp render times to two frames, + not one + +Because the buffer queue may grow by an extra frame in triple buffering, +we need measurements exceeding one frame to indicate when and by how much +the scaling is necessary. +--- + clutter/clutter/clutter-frame-clock.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 384537e41..6a11d4b66 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -385,7 +385,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + + MAX (swap_to_rendering_done_us, swap_to_flip_us), + frame_clock->shortterm_max_update_duration_us, +- frame_clock->refresh_interval_us); ++ 2 * frame_clock->refresh_interval_us); + + maybe_update_longterm_max_duration_us (frame_clock, frame_info); + +@@ -471,7 +471,7 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) + frame_clock->vblank_duration_us + + clutter_max_render_time_constant_us; + +- max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); ++ max_render_time_us = CLAMP (max_render_time_us, 0, 2 * refresh_interval_us); + + return max_render_time_us; + } +-- +2.45.0 + + +From cfaee218edf230a29d443773ff40c94d45ee2dea Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 10 Sep 2020 16:34:53 +0800 +Subject: [PATCH 21/34] clutter/frame-clock: Add triple buffering support + +--- + clutter/clutter/clutter-frame-clock.c | 198 ++++++++++++++++++++++---- + 1 file changed, 170 insertions(+), 28 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 6a11d4b66..32d2b2d87 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -70,7 +70,10 @@ typedef enum _ClutterFrameClockState + CLUTTER_FRAME_CLOCK_STATE_IDLE, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, + CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, +- CLUTTER_FRAME_CLOCK_STATE_DISPATCHED, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW, ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, + } ClutterFrameClockState; + + struct _ClutterFrameClock +@@ -91,6 +94,7 @@ struct _ClutterFrameClock + ClutterFrameClockMode mode; + + int64_t last_dispatch_time_us; ++ int64_t prev_last_dispatch_time_us; + int64_t last_dispatch_lateness_us; + int64_t last_presentation_time_us; + int64_t next_update_time_us; +@@ -110,6 +114,7 @@ struct _ClutterFrameClock + int64_t vblank_duration_us; + /* Last KMS buffer submission time. */ + int64_t last_flip_time_us; ++ int64_t prev_last_flip_time_us; + + /* Last time we promoted short-term maximum to long-term one */ + int64_t longterm_promotion_us; +@@ -364,14 +369,35 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + frame_info->has_valid_gpu_rendering_duration) + { + int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; ++ int64_t dispatch_time_us = 0, flip_time_us = 0; ++ ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ g_warn_if_reached (); ++ G_GNUC_FALLTHROUGH; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ dispatch_time_us = frame_clock->last_dispatch_time_us; ++ flip_time_us = frame_clock->last_flip_time_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ dispatch_time_us = frame_clock->prev_last_dispatch_time_us; ++ flip_time_us = frame_clock->prev_last_flip_time_us; ++ break; ++ } + + dispatch_to_swap_us = + frame_info->cpu_time_before_buffer_swap_us - +- frame_clock->last_dispatch_time_us; ++ dispatch_time_us; + swap_to_rendering_done_us = + frame_info->gpu_rendering_duration_ns / 1000; + swap_to_flip_us = +- frame_clock->last_flip_time_us - ++ flip_time_us - + frame_info->cpu_time_before_buffer_swap_us; + + CLUTTER_NOTE (FRAME_TIMINGS, +@@ -412,10 +438,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + } + +@@ -433,10 +471,22 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + } + +@@ -451,7 +501,14 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) + if (!frame_clock->ever_got_measurements || + G_UNLIKELY (clutter_paint_debug_flags & + CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) +- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; ++ { ++ int64_t ret = refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; ++ ++ if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE) ++ ret += refresh_interval_us; ++ ++ return ret; ++ } + + /* Max render time shows how early the frame clock needs to be dispatched + * to make it to the predicted next presentation time. It is an estimate of +@@ -488,7 +545,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + int64_t min_render_time_allowed_us; + int64_t max_render_time_allowed_us; + int64_t next_presentation_time_us; +- int64_t next_smooth_presentation_time_us; ++ int64_t next_smooth_presentation_time_us = 0; + int64_t next_update_time_us; + + now_us = g_get_monotonic_time (); +@@ -533,7 +590,27 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + * + */ + last_presentation_time_us = frame_clock->last_presentation_time_us; +- next_smooth_presentation_time_us = last_presentation_time_us + refresh_interval_us; ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ refresh_interval_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ 2 * refresh_interval_us; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ next_smooth_presentation_time_us = last_presentation_time_us + ++ 3 * refresh_interval_us; ++ break; ++ } ++ + next_presentation_time_us = next_smooth_presentation_time_us; + + /* +@@ -732,7 +809,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->pending_reschedule_now = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + break; + } + +@@ -768,10 +855,18 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ next_update_time_us = g_get_monotonic_time (); ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = TRUE; + return; +@@ -800,7 +895,6 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + + frame_clock->next_update_time_us = next_update_time_us; + g_source_set_ready_time (frame_clock->source, next_update_time_us); +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + } + + void +@@ -822,11 +916,23 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + return; + case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ calculate_next_update_time_us (frame_clock, ++ &next_update_time_us, ++ &frame_clock->next_presentation_time_us, ++ &frame_clock->next_frame_deadline_us); ++ frame_clock->is_next_presentation_time_valid = ++ (frame_clock->next_presentation_time_us != 0); ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; + return; + } +@@ -855,7 +961,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + + frame_clock->next_update_time_us = next_update_time_us; + g_source_set_ready_time (frame_clock->source, next_update_time_us); +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + } + + void +@@ -871,6 +976,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + break; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + frame_clock->pending_reschedule = TRUE; +@@ -881,7 +988,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->pending_reschedule = TRUE; ++ frame_clock->pending_reschedule_now = TRUE; ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + break; + } + +@@ -938,10 +1052,27 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + } + #endif + ++ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us; + frame_clock->last_dispatch_time_us = time_us; + g_source_set_ready_time (frame_clock->source, -1); + +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED; ++ switch (frame_clock->state) ++ { ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ g_warn_if_reached (); ++ return; ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; ++ break; ++ } + + frame_count = frame_clock->frame_count++; + +@@ -972,26 +1103,36 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, + result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); + COGL_TRACE_END (ClutterFrameClockFrame); + +- switch (frame_clock->state) ++ switch (result) + { +- case CLUTTER_FRAME_CLOCK_STATE_INIT: +- g_warn_if_reached (); ++ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: + break; +- case CLUTTER_FRAME_CLOCK_STATE_IDLE: +- /* Presentation completed synchronously in the above listener */ +- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: +- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: +- break; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED: +- switch (result) ++ case CLUTTER_FRAME_RESULT_IDLE: ++ /* The frame was aborted; nothing to paint/present */ ++ switch (frame_clock->state) + { +- case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: ++ case CLUTTER_FRAME_CLOCK_STATE_INIT: ++ case CLUTTER_FRAME_CLOCK_STATE_IDLE: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: ++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: ++ g_warn_if_reached (); + break; +- case CLUTTER_FRAME_RESULT_IDLE: +- /* The frame was aborted; nothing to paint/present */ ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; + maybe_reschedule_update (frame_clock); + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; ++ maybe_reschedule_update (frame_clock); ++ break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: ++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; ++ maybe_reschedule_update (frame_clock); ++ break; + } + break; + } +@@ -1027,6 +1168,7 @@ void + clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, + int64_t flip_time_us) + { ++ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; + frame_clock->last_flip_time_us = flip_time_us; + } + +-- +2.45.0 + + +From 92b566e4d992c32ff344d5cbfde55a3f2fe721e1 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Fri, 10 Dec 2021 16:28:04 +0800 +Subject: [PATCH 22/34] clutter/frame-clock: Log N-buffers in + CLUTTTER_DEBUG=frame-timings + +--- + clutter/clutter/clutter-frame-clock.c | 12 ++++++++++-- + 1 file changed, 10 insertions(+), 2 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 32d2b2d87..e3fb23086 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -279,6 +279,12 @@ void + clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + ClutterFrameInfo *frame_info) + { ++#ifdef CLUTTER_ENABLE_DEBUG ++ const char *debug_state = ++ frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ? ++ "Triple buffering" : "Double buffering"; ++#endif ++ + COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, + "Clutter::FrameClock::presented()"); + COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, +@@ -401,7 +407,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + frame_info->cpu_time_before_buffer_swap_us; + + CLUTTER_NOTE (FRAME_TIMINGS, +- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", ++ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", ++ debug_state, + frame_clock->last_dispatch_lateness_us, + dispatch_to_swap_us, + swap_to_rendering_done_us, +@@ -420,7 +427,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + } + else + { +- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", ++ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs", ++ debug_state, + frame_clock->last_dispatch_lateness_us); + } + +-- +2.45.0 + + +From 9209eba42a0f37c0d178362152d93b2729f93ed4 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 7 Sep 2021 19:08:15 +0800 +Subject: [PATCH 23/34] clutter/frame: Add ClutterFrameHint to ClutterFrame + +This will allow the backend to provide performance hints to the frame +clock in future. +--- + clutter/clutter/clutter-frame-clock.h | 6 ++++++ + clutter/clutter/clutter-frame-private.h | 1 + + clutter/clutter/clutter-frame.c | 13 +++++++++++++ + clutter/clutter/clutter-frame.h | 7 +++++++ + 4 files changed, 27 insertions(+) + +diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h +index a7be5ef31..5adf99509 100644 +--- a/clutter/clutter/clutter-frame-clock.h ++++ b/clutter/clutter/clutter-frame-clock.h +@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult + CLUTTER_FRAME_RESULT_IDLE, + } ClutterFrameResult; + ++typedef enum _ClutterFrameHint ++{ ++ CLUTTER_FRAME_HINT_NONE = 0, ++ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0, ++} ClutterFrameHint; ++ + #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ()) + CLUTTER_EXPORT + G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, +diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h +index ef66b874e..ce140560a 100644 +--- a/clutter/clutter/clutter-frame-private.h ++++ b/clutter/clutter/clutter-frame-private.h +@@ -36,6 +36,7 @@ struct _ClutterFrame + + gboolean has_result; + ClutterFrameResult result; ++ ClutterFrameHint hints; + }; + + CLUTTER_EXPORT +diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c +index 7436f9f18..53c289b2c 100644 +--- a/clutter/clutter/clutter-frame.c ++++ b/clutter/clutter/clutter-frame.c +@@ -115,3 +115,16 @@ clutter_frame_set_result (ClutterFrame *frame, + frame->result = result; + frame->has_result = TRUE; + } ++ ++void ++clutter_frame_set_hint (ClutterFrame *frame, ++ ClutterFrameHint hint) ++{ ++ frame->hints |= hint; ++} ++ ++ClutterFrameHint ++clutter_frame_get_hints (ClutterFrame *frame) ++{ ++ return frame->hints; ++} +diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h +index 34f0770bd..c7b3d02ac 100644 +--- a/clutter/clutter/clutter-frame.h ++++ b/clutter/clutter/clutter-frame.h +@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame, + CLUTTER_EXPORT + gboolean clutter_frame_has_result (ClutterFrame *frame); + ++CLUTTER_EXPORT ++void clutter_frame_set_hint (ClutterFrame *frame, ++ ClutterFrameHint hint); ++ ++CLUTTER_EXPORT ++ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame); ++ + G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref) +-- +2.45.0 + + +From fb3a7d3fba001d2154b38bac0abe660458e50b0d Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 7 Sep 2021 19:10:26 +0800 +Subject: [PATCH 24/34] backends: Flag that the frame attempted direct scanout + +We need this hint whether direct scanout succeeds or fails because it's +the mechanism by which we will tell the clock to enforce double buffering, +thus making direct scanout possible on future frames. Triple buffering +will be disabled until such time that direct scanout is not being attempted. +--- + src/backends/meta-stage-impl.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c +index 7aa24439d..727e1a5f3 100644 +--- a/src/backends/meta-stage-impl.c ++++ b/src/backends/meta-stage-impl.c +@@ -774,6 +774,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window, + { + g_autoptr (GError) error = NULL; + ++ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); ++ + if (meta_stage_impl_scanout_view (stage_impl, + stage_view, + scanout, +-- +2.45.0 + + +From 70d35955aca38a14aafef8bbe9eefde93e508eec Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 7 Sep 2021 19:15:18 +0800 +Subject: [PATCH 25/34] clutter: Pass ClutterFrameHint(s) to the frame clock + +--- + clutter/clutter/clutter-frame-clock.c | 8 ++++++-- + clutter/clutter/clutter-frame-clock.h | 5 +++-- + clutter/clutter/clutter-stage-view.c | 11 +++++++++-- + 3 files changed, 18 insertions(+), 6 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index e3fb23086..84d2f1f31 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -116,6 +116,8 @@ struct _ClutterFrameClock + int64_t last_flip_time_us; + int64_t prev_last_flip_time_us; + ++ ClutterFrameHint last_flip_hints; ++ + /* Last time we promoted short-term maximum to long-term one */ + int64_t longterm_promotion_us; + /* Long-term maximum update duration */ +@@ -1173,11 +1175,13 @@ frame_clock_source_dispatch (GSource *source, + } + + void +-clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, +- int64_t flip_time_us) ++clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, ++ int64_t flip_time_us, ++ ClutterFrameHint hints) + { + frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; + frame_clock->last_flip_time_us = flip_time_us; ++ frame_clock->last_flip_hints = hints; + } + + GString * +diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h +index 5adf99509..bfc89bde0 100644 +--- a/clutter/clutter/clutter-frame-clock.h ++++ b/clutter/clutter/clutter-frame-clock.h +@@ -108,7 +108,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, + CLUTTER_EXPORT + float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); + +-void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, +- int64_t flip_time_us); ++void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, ++ int64_t flip_time_us, ++ ClutterFrameHint hints); + + GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); +diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c +index f5188e2ac..d53e37785 100644 +--- a/clutter/clutter/clutter-stage-view.c ++++ b/clutter/clutter/clutter-stage-view.c +@@ -898,14 +898,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, + + _clutter_stage_window_redraw_view (stage_window, view, frame); + +- clutter_frame_clock_record_flip_time (frame_clock, +- g_get_monotonic_time ()); ++ clutter_frame_clock_record_flip (frame_clock, ++ g_get_monotonic_time (), ++ clutter_frame_get_hints (frame)); + + clutter_stage_emit_after_paint (stage, view, frame); + + if (_clutter_context_get_show_fps ()) + end_frame_timing_measurement (view); + } ++ else ++ { ++ clutter_frame_clock_record_flip (frame_clock, ++ g_get_monotonic_time (), ++ clutter_frame_get_hints (frame)); ++ } + + _clutter_stage_window_finish_frame (stage_window, view, frame); + +-- +2.45.0 + + +From b1046b6b838bc4cb6ff089e5817455d763a874f1 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 7 Sep 2021 19:15:55 +0800 +Subject: [PATCH 26/34] clutter/frame-clock: Throttle back to double buffering + for direct scanout + +There's no compositing during direct scanout so the "render" time is zero. +Thus there is no need to implement triple buffering for direct scanouts. +Stick to double buffering and enjoy the lower latency. +--- + clutter/clutter/clutter-frame-clock.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 84d2f1f31..c02eb85e2 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -934,6 +934,13 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ if (frame_clock->last_flip_hints & CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED) ++ { ++ /* Force double buffering, disable triple buffering */ ++ frame_clock->pending_reschedule = TRUE; ++ return; ++ } ++ + calculate_next_update_time_us (frame_clock, + &next_update_time_us, + &frame_clock->next_presentation_time_us, +-- +2.45.0 + + +From 2a3bb7638d72bd262a70b3833ee82319646f14f1 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 10 Mar 2022 16:44:14 +0800 +Subject: [PATCH 27/34] clutter/frame-clock: Add environment variable + MUTTER_DEBUG_TRIPLE_BUFFERING + +With possible values {never, auto, always} where auto is the default. + +This also allows some unification with automatic throttling for +direct scanout. +--- + clutter/clutter/clutter-frame-clock.c | 55 ++++++++++++++++++++++----- + 1 file changed, 45 insertions(+), 10 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index c02eb85e2..142f670f5 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -42,6 +42,15 @@ enum + + static guint signals[N_SIGNALS]; + ++typedef enum ++{ ++ TRIPLE_BUFFERING_MODE_NEVER, ++ TRIPLE_BUFFERING_MODE_AUTO, ++ TRIPLE_BUFFERING_MODE_ALWAYS, ++} TripleBufferingMode; ++ ++static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; ++ + #define SYNC_DELAY_FALLBACK_FRACTION 0.875 + + #define MINIMUM_REFRESH_RATE 30.f +@@ -911,6 +920,12 @@ void + clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + { + int64_t next_update_time_us = -1; ++ TripleBufferingMode current_mode = triple_buffering_mode; ++ ++ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && ++ (frame_clock->last_flip_hints & ++ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED)) ++ current_mode = TRIPLE_BUFFERING_MODE_NEVER; + + if (frame_clock->inhibit_count > 0) + { +@@ -934,20 +949,31 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: +- if (frame_clock->last_flip_hints & CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED) ++ switch (current_mode) + { +- /* Force double buffering, disable triple buffering */ ++ case TRIPLE_BUFFERING_MODE_NEVER: + frame_clock->pending_reschedule = TRUE; + return; ++ case TRIPLE_BUFFERING_MODE_AUTO: ++ calculate_next_update_time_us (frame_clock, ++ &next_update_time_us, ++ &frame_clock->next_presentation_time_us, ++ &frame_clock->next_frame_deadline_us); ++ frame_clock->is_next_presentation_time_valid = ++ (frame_clock->next_presentation_time_us != 0); ++ frame_clock->has_next_frame_deadline = ++ (frame_clock->next_frame_deadline_us != 0); ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; ++ break; ++ case TRIPLE_BUFFERING_MODE_ALWAYS: ++ next_update_time_us = g_get_monotonic_time (); ++ frame_clock->next_presentation_time_us = 0; ++ frame_clock->is_next_presentation_time_valid = FALSE; ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; ++ break; + } +- +- calculate_next_update_time_us (frame_clock, +- &next_update_time_us, +- &frame_clock->next_presentation_time_us, +- &frame_clock->next_frame_deadline_us); +- frame_clock->is_next_presentation_time_valid = +- (frame_clock->next_presentation_time_us != 0); +- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; +@@ -1389,6 +1415,15 @@ static void + clutter_frame_clock_class_init (ClutterFrameClockClass *klass) + { + GObjectClass *object_class = G_OBJECT_CLASS (klass); ++ const char *mode_str; ++ ++ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING"); ++ if (!g_strcmp0 (mode_str, "never")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER; ++ else if (!g_strcmp0 (mode_str, "auto")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; ++ else if (!g_strcmp0 (mode_str, "always")) ++ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS; + + object_class->dispose = clutter_frame_clock_dispose; + +-- +2.45.0 + + +From 98b65e3c59979f616e5ff99b3eda8d1375a59e23 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 30 Jun 2022 18:56:06 +0800 +Subject: [PATCH 28/34] tests/native-kms-render: Fix failing client-scanout + test + +It was assuming an immediate transition from compositing (triple +buffering) to direct scanout (double buffering), whereas there is +a one frame delay in that transition as the buffer queue shrinks. +We don't lose any frames in the transition. +--- + src/tests/native-kms-render.c | 106 ++++++++++++++++++++++++++++------ + 1 file changed, 87 insertions(+), 19 deletions(-) + diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c -index f5ebc23fec7590fd559c64049363024a06883bcd..2f870fdc331bf818b6215907d3828196b713d30f 100644 +index f5ebc23fe..2f870fdc3 100644 --- a/src/tests/native-kms-render.c +++ b/src/tests/native-kms-render.c @@ -39,6 +39,8 @@ @@ -1878,3 +2576,496 @@ index f5ebc23fec7590fd559c64049363024a06883bcd..2f870fdc331bf818b6215907d3828196 .loop = g_main_loop_new (NULL, FALSE), }; +-- +2.45.0 + + +From e39543fb6a6daba10faa195bb7aabf276d9698db Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Thu, 22 Jun 2023 15:19:53 +0800 +Subject: [PATCH 29/34] onscreen/native: Avoid callbacks on "detached" + onscreens + +Detached onscreens have no valid view so avoid servicing callbacks on +them during/after sleep mode. As previously mentioned in 45bda2d969f. + +Closes: https://launchpad.net/bugs/2020049 +--- + src/backends/native/meta-onscreen-native.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index 65e48f2ea..b35021c4f 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -1525,6 +1525,7 @@ try_post_latest_swap (CoglOnscreen *onscreen) + MetaFrameNative *frame_native; + + if (onscreen_native->next_post.frame == NULL || ++ onscreen_native->view == NULL || + meta_kms_is_shutting_down (kms)) + return; + +-- +2.45.0 + + +From 7f334ef6c6d33baffad04887affe5583524393a0 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Tue, 18 Jul 2023 16:08:25 +0800 +Subject: [PATCH 30/34] clutter/frame-clock: Record measurements of zero for + cursor-only updates + +But only if we've ever got actual swap measurements +(COGL_FEATURE_ID_TIMESTAMP_QUERY). If it's supported then we now drop to +double buffering and get optimal latency on a burst of cursor-only +updates. + +Closes: https://launchpad.net/bugs/2023363 +--- + clutter/clutter/clutter-frame-clock.c | 28 +++++++++++++++------------ + 1 file changed, 16 insertions(+), 12 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 142f670f5..eed8fd2a8 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -260,10 +260,6 @@ static void + maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, + ClutterFrameInfo *frame_info) + { +- /* Do not update long-term max if there has been no measurement */ +- if (!frame_clock->shortterm_max_update_duration_us) +- return; +- + if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) < + G_USEC_PER_SEC) + return; +@@ -382,8 +378,9 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + + frame_clock->got_measurements_last_frame = FALSE; + +- if (frame_info->cpu_time_before_buffer_swap_us != 0 && +- frame_info->has_valid_gpu_rendering_duration) ++ if ((frame_info->cpu_time_before_buffer_swap_us != 0 && ++ frame_info->has_valid_gpu_rendering_duration) || ++ frame_clock->ever_got_measurements) + { + int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; + int64_t dispatch_time_us = 0, flip_time_us = 0; +@@ -408,14 +405,21 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, + break; + } + +- dispatch_to_swap_us = +- frame_info->cpu_time_before_buffer_swap_us - +- dispatch_time_us; ++ if (frame_info->cpu_time_before_buffer_swap_us == 0) ++ { ++ /* Cursor-only updates with no "swap" or "flip" */ ++ dispatch_to_swap_us = 0; ++ swap_to_flip_us = 0; ++ } ++ else ++ { ++ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us - ++ dispatch_time_us; ++ swap_to_flip_us = flip_time_us - ++ frame_info->cpu_time_before_buffer_swap_us; ++ } + swap_to_rendering_done_us = + frame_info->gpu_rendering_duration_ns / 1000; +- swap_to_flip_us = +- flip_time_us - +- frame_info->cpu_time_before_buffer_swap_us; + + CLUTTER_NOTE (FRAME_TIMINGS, + "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", +-- +2.45.0 + + +From 295a866d5810b39bcab0e4fe0f7a31d9f0befebd Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 26 Jul 2023 17:27:10 +0800 +Subject: [PATCH 31/34] backends/native: Make await_flush later and inlined + +"Later" as in after GL is finished rendering so as to not block the deadline +timer from updating the cursor smoothly during long GL renders. Indeed this +could make a primary plane frame late in some cases but that's only when +going from idle to active, so there is no previous frame to observe stutter +within the primary plane. It does however fix observable stutter in the +cursor plane by not dropping/stalling an otherwise good cursor update in a +continuous stream of cursor updates. + +"Inlined" as in we don't need most of `meta_kms_device_await_flush`. The +important part is that it calls `disarm_crtc_frame_deadline_timer` to avoid +attempting two posts at once. So that's all that is kept. + +This also fixes a deadlock in triple buffering. By not cancelling the +cursor update during a GL render we're also not cancelling a primary plane +update that might have already been piggybacked onto it by `queue_update`. + +Cherry picked from !3149 +--- + src/backends/native/meta-kms-impl-device.c | 4 +++- + src/backends/native/meta-onscreen-native.c | 4 ---- + 2 files changed, 3 insertions(+), 5 deletions(-) + +diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c +index b15eee14d..05bc89e83 100644 +--- a/src/backends/native/meta-kms-impl-device.c ++++ b/src/backends/native/meta-kms-impl-device.c +@@ -1559,9 +1559,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, + meta_kms_update_merge_from (crtc_frame->pending_update, update); + meta_kms_update_free (update); + update = g_steal_pointer (&crtc_frame->pending_update); +- disarm_crtc_frame_deadline_timer (crtc_frame); + } + ++ if (crtc_frame->deadline.armed) ++ disarm_crtc_frame_deadline_timer (crtc_frame); ++ + meta_kms_device_handle_flush (priv->device, latch_crtc); + + feedback = do_process (impl_device, latch_crtc, update, flags); +diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c +index b35021c4f..9836663d0 100644 +--- a/src/backends/native/meta-onscreen-native.c ++++ b/src/backends/native/meta-onscreen-native.c +@@ -1899,11 +1899,7 @@ meta_onscreen_native_before_redraw (CoglOnscreen *onscreen, + ClutterFrame *frame) + { + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); +- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); +- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + +- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), +- kms_crtc); + maybe_update_frame_sync (onscreen_native, frame); + } + +-- +2.45.0 + + +From 2602e42ca255847123a567deb30dd51431fecb64 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 17 Jan 2024 17:21:03 +0800 +Subject: [PATCH 32/34] clutter/frame-clock: Optimize latency for platforms + missing TIMESTAMP_QUERY + +Previously if we had no measurements then `compute_max_render_time_us` +would pessimise its answer to ensure triple buffering could be reached: +``` +if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE) + ret += refresh_interval_us; +``` +But that also meant entering triple buffering even when not required. + +Now we make `compute_max_render_time_us` more honest and return failure +if the answer isn't known (or is disabled). This in turn allows us to +optimize `calculate_next_update_time_us` for this special case, ensuring +triple buffering can be used, but isn't blindly always used. + +This makes a visible difference to the latency when dragging windows in +Xorg, but will also help Wayland sessions on platforms lacking +TIMESTAMP_QUERY such as Raspberry Pi. +--- + clutter/clutter/clutter-frame-clock.c | 69 +++++++++++++++++---------- + 1 file changed, 45 insertions(+), 24 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index eed8fd2a8..a6c7ecea2 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -513,25 +513,18 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) + } + } + +-static int64_t +-clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) ++static gboolean ++clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock, ++ int64_t *max_render_time_us) + { + int64_t refresh_interval_us; +- int64_t max_render_time_us; + + refresh_interval_us = frame_clock->refresh_interval_us; + + if (!frame_clock->ever_got_measurements || + G_UNLIKELY (clutter_paint_debug_flags & + CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) +- { +- int64_t ret = refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; +- +- if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE) +- ret += refresh_interval_us; +- +- return ret; +- } ++ return FALSE; + + /* Max render time shows how early the frame clock needs to be dispatched + * to make it to the predicted next presentation time. It is an estimate of +@@ -545,15 +538,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) + * - The duration of vertical blank. + * - A constant to account for variations in the above estimates. + */ +- max_render_time_us = ++ *max_render_time_us = + MAX (frame_clock->longterm_max_update_duration_us, + frame_clock->shortterm_max_update_duration_us) + + frame_clock->vblank_duration_us + + clutter_max_render_time_constant_us; + +- max_render_time_us = CLAMP (max_render_time_us, 0, 2 * refresh_interval_us); ++ *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us); + +- return max_render_time_us; ++ return TRUE; + } + + static void +@@ -570,6 +563,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + int64_t next_presentation_time_us; + int64_t next_smooth_presentation_time_us = 0; + int64_t next_update_time_us; ++ gboolean max_render_time_is_known; + + now_us = g_get_monotonic_time (); + +@@ -589,10 +583,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + + min_render_time_allowed_us = refresh_interval_us / 2; +- max_render_time_allowed_us = +- clutter_frame_clock_compute_max_render_time_us (frame_clock); + +- if (min_render_time_allowed_us > max_render_time_allowed_us) ++ max_render_time_is_known = ++ clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_allowed_us); ++ ++ if (max_render_time_is_known && ++ min_render_time_allowed_us > max_render_time_allowed_us) + min_render_time_allowed_us = max_render_time_allowed_us; + + /* +@@ -712,6 +709,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, + } + else + { ++ /* If the max render time isn't known then using the current value of ++ * next_presentation_time_us is suboptimal. Targeting always one frame ++ * prior to that we'd lose the ability to scale up to triple buffering ++ * on late presentation. But targeting two frames prior we would be ++ * always triple buffering even when not required. ++ * So the algorithm for deciding when to scale up to triple buffering ++ * in the absence of render time measurements is to simply target full ++ * frame rate. If we're keeping up then we'll stay double buffering. If ++ * we're not keeping up then this will switch us to triple buffering. ++ */ ++ if (!max_render_time_is_known) ++ { ++ max_render_time_allowed_us = ++ refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; ++ next_presentation_time_us = ++ last_presentation_time_us + refresh_interval_us; ++ } ++ + while (next_presentation_time_us - min_render_time_allowed_us < now_us) + next_presentation_time_us += refresh_interval_us; + +@@ -743,7 +758,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, + + refresh_interval_us = frame_clock->refresh_interval_us; + +- if (frame_clock->last_presentation_time_us == 0) ++ if (frame_clock->last_presentation_time_us == 0 || ++ !clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_allowed_us)) + { + *out_next_update_time_us = + frame_clock->last_dispatch_time_us ? +@@ -756,9 +773,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, + return; + } + +- max_render_time_allowed_us = +- clutter_frame_clock_compute_max_render_time_us (frame_clock); +- + last_presentation_time_us = frame_clock->last_presentation_time_us; + next_presentation_time_us = last_presentation_time_us + refresh_interval_us; + +@@ -1224,12 +1238,19 @@ clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, + GString * + clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) + { ++ int64_t max_render_time_us; + int64_t max_update_duration_us; + GString *string; + +- string = g_string_new (NULL); +- g_string_append_printf (string, "Max render time: %ld µs", +- clutter_frame_clock_compute_max_render_time_us (frame_clock)); ++ string = g_string_new ("Max render time: "); ++ if (!clutter_frame_clock_compute_max_render_time_us (frame_clock, ++ &max_render_time_us)) ++ { ++ g_string_append (string, "unknown"); ++ return string; ++ } ++ ++ g_string_append_printf (string, "%ld µs", max_render_time_us); + + if (frame_clock->got_measurements_last_frame) + g_string_append_printf (string, " ="); +-- +2.45.0 + + +From 04e002f472ef46e258f8c971f157644e170d84e2 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 24 Apr 2024 18:21:01 +0800 +Subject: [PATCH 33/34] clutter/frame-clock: Break a feedback loop between VRR + and triple buffering + +VRR calls `clutter_frame_clock_schedule_update_now` which would keep +the buffer queue full, which in turn prevented direct scanout mode. +Because OnscreenNative currently only supports direct scanout with +double buffering. + +We now break that feedback loop by preventing triple buffering from +being scheduled when the frame clock mode becomes variable. + +Long term this could also be solved by supporting triple buffering in +direct scanout mode. But whether or not that would be optimal given +the latency penalty remains to be seen. +--- + clutter/clutter/clutter-frame-clock.c | 31 ++++++++++++++++++++++++--- + 1 file changed, 28 insertions(+), 3 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index a6c7ecea2..129d4f533 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -875,6 +875,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock) + maybe_reschedule_update (frame_clock); + } + ++static gboolean ++want_triple_buffering (ClutterFrameClock *frame_clock) ++{ ++ switch (triple_buffering_mode) ++ { ++ case TRIPLE_BUFFERING_MODE_NEVER: ++ return FALSE; ++ case TRIPLE_BUFFERING_MODE_AUTO: ++ return frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_FIXED && ++ !(frame_clock->last_flip_hints & ++ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); ++ case TRIPLE_BUFFERING_MODE_ALWAYS: ++ return TRUE; ++ } ++ ++ g_assert_not_reached (); ++ return FALSE; ++} ++ + void + clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + { +@@ -897,12 +916,19 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; +- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + next_update_time_us = g_get_monotonic_time (); + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; + break; ++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: ++ if (want_triple_buffering (frame_clock)) ++ { ++ frame_clock->state = ++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; ++ break; ++ } ++ G_GNUC_FALLTHROUGH; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = TRUE; +@@ -941,8 +967,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + TripleBufferingMode current_mode = triple_buffering_mode; + + if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && +- (frame_clock->last_flip_hints & +- CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED)) ++ !want_triple_buffering (frame_clock)) + current_mode = TRIPLE_BUFFERING_MODE_NEVER; + + if (frame_clock->inhibit_count > 0) +-- +2.45.0 + + +From 663f19bc02c1b4e3d1a67b4ad72d644f9b9d6970 Mon Sep 17 00:00:00 2001 +From: Daniel van Vugt <daniel.van.vugt@canonical.com> +Date: Wed, 24 Apr 2024 18:42:54 +0800 +Subject: [PATCH 34/34] clutter/frame-clock: Remove some redundant logic + +Which became unused with the introduction of VRR. +--- + clutter/clutter/clutter-frame-clock.c | 12 ++---------- + 1 file changed, 2 insertions(+), 10 deletions(-) + +diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c +index 129d4f533..6bd83101e 100644 +--- a/clutter/clutter/clutter-frame-clock.c ++++ b/clutter/clutter/clutter-frame-clock.c +@@ -917,7 +917,6 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + return; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: +- next_update_time_us = g_get_monotonic_time (); + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; + break; +@@ -998,14 +997,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + frame_clock->pending_reschedule = TRUE; + return; + case TRIPLE_BUFFERING_MODE_AUTO: +- calculate_next_update_time_us (frame_clock, +- &next_update_time_us, +- &frame_clock->next_presentation_time_us, +- &frame_clock->next_frame_deadline_us); +- frame_clock->is_next_presentation_time_valid = +- (frame_clock->next_presentation_time_us != 0); +- frame_clock->has_next_frame_deadline = +- (frame_clock->next_frame_deadline_us != 0); + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; + break; +@@ -1015,7 +1006,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + frame_clock->is_next_presentation_time_valid = FALSE; + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; +- break; ++ goto got_update_time; + } + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: +@@ -1043,6 +1034,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) + break; + } + ++got_update_time: + g_warn_if_fail (next_update_time_us != -1); + + frame_clock->next_update_time_us = next_update_time_us; +-- +2.45.0 + |