summarylogtreecommitdiffstats
path: root/mr1441.patch
diff options
context:
space:
mode:
authorMingi Sung2024-05-08 22:03:56 +0900
committerMingi Sung2024-05-08 22:03:56 +0900
commit9d860be09e5db9aaa86febd9a3b8574d51d680d7 (patch)
treef2ebd1fa2ac32cabcaf54ae909616aa17a542c14 /mr1441.patch
parentc35008e2a13891eb73763936a8a8ab7c80667bf1 (diff)
downloadaur-9d860be09e5db9aaa86febd9a3b8574d51d680d7.tar.gz
Update mr1441 and mr3729
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
Diffstat (limited to 'mr1441.patch')
-rw-r--r--mr1441.patch3523
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
+