summarylogtreecommitdiffstats
path: root/mr1441.patch
diff options
context:
space:
mode:
authorMingi Sung2022-10-27 23:41:22 +0900
committerMingi Sung2022-11-01 21:38:58 +0900
commit372a78131f04efb1c584a72277b4461c0efa2b99 (patch)
tree4b6fec144012f89df0f119fc3454b4f4e46137bd /mr1441.patch
parent1b0f57b15437ec406d00b5991b0c177f4a6ef6bf (diff)
downloadaur-372a78131f04efb1c584a72277b4461c0efa2b99.tar.gz
Sync to gnome-43
Signed-off-by: Mingi Sung <FiestaLake@protonmail.com>
Diffstat (limited to 'mr1441.patch')
-rw-r--r--mr1441.patch2785
1 files changed, 2785 insertions, 0 deletions
diff --git a/mr1441.patch b/mr1441.patch
new file mode 100644
index 000000000000..bd521009d96d
--- /dev/null
+++ b/mr1441.patch
@@ -0,0 +1,2785 @@
+Author: Daniel van Vugt <daniel.van.vugt@canonical.com>
+Source: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441
+Editor: Mingi Sung <FiestaLake@protonmail.com>
+Commit: 01f700fc1c5bd9f6265577d0b86f2f407e256cc4
+Last Updated: 10/27/22 (Mutter 43.0+r87+g986d3be7a-1)
+---
+
+Use triple buffering if and when the previous frame is running late.
+This means the next frame will be dispatched on time instead of also starting
+late.
+
+It also triggers a GPU clock boost if deemed necessary by the driver.
+Although frequency scaling is not required to get a performance gain here
+because even a fixed frequency GPU will benefit from not over-sleeping anymore.
+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 4d441c364b2f86d601a53a3dc1c46f5a5cd4092d..ee05b36de0973b8cbe99362eb1b76a6f49a8e453 100644
+--- a/clutter/clutter/clutter-frame-clock.c
++++ b/clutter/clutter/clutter-frame-clock.c
+@@ -45,6 +45,8 @@ typedef struct _EstimateQueue
+ int next_index;
+ } EstimateQueue;
+
++static gboolean triple_buffering_disabled = FALSE;
++
+ #define SYNC_DELAY_FALLBACK_FRACTION 0.875
+
+ typedef struct _ClutterFrameListener
+@@ -65,8 +67,9 @@ typedef enum _ClutterFrameClockState
+ CLUTTER_FRAME_CLOCK_STATE_INIT,
+ CLUTTER_FRAME_CLOCK_STATE_IDLE,
+ CLUTTER_FRAME_CLOCK_STATE_SCHEDULED,
+- 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_TWO,
+ } ClutterFrameClockState;
+
+ struct _ClutterFrameClock
+@@ -96,6 +99,8 @@ struct _ClutterFrameClock
+ /* Last KMS buffer submission time. */
+ int64_t last_flip_time_us;
+
++ ClutterFrameHint last_flip_hints;
++
+ /* Last few durations between dispatch start and buffer swap. */
+ EstimateQueue dispatch_to_swap_us;
+ /* Last few durations between buffer swap and GPU rendering finish. */
+@@ -228,6 +233,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,
+ "Frame Clock (presented)");
+
+@@ -290,7 +301,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
+ frame_info->cpu_time_before_buffer_swap_us;
+
+ CLUTTER_NOTE (FRAME_TIMINGS,
+- "dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
++ "%s: dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
++ debug_state,
+ dispatch_to_swap_us,
+ swap_to_rendering_done_us,
+ swap_to_flip_us);
+@@ -304,6 +316,10 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
+
+ frame_clock->got_measurements_last_frame = TRUE;
+ }
++ else
++ {
++ CLUTTER_NOTE (FRAME_TIMINGS, "%s", debug_state);
++ }
+
+ if (frame_info->refresh_rate > 1.0)
+ {
+@@ -318,11 +334,18 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ 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_TWO:
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
++ maybe_reschedule_update (frame_clock);
++ break;
+ }
+ }
+
+@@ -338,11 +361,18 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock)
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
+ 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_TWO:
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
++ maybe_reschedule_update (frame_clock);
++ break;
+ }
+ }
+
+@@ -354,6 +384,7 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock)
+ int64_t max_swap_to_rendering_done_us = 0;
+ int64_t max_swap_to_flip_us = 0;
+ int64_t max_render_time_us;
++ int buffer_queue_latency_frames = 0;
+ int i;
+
+ refresh_interval_us = frame_clock->refresh_interval_us;
+@@ -376,6 +407,27 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock)
+ frame_clock->swap_to_flip_us.values[i]);
+ }
+
++ switch (frame_clock->state)
++ {
++ case CLUTTER_FRAME_CLOCK_STATE_INIT:
++ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
++ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
++ buffer_queue_latency_frames = 0;
++ break;
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
++ buffer_queue_latency_frames = 1;
++ break;
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
++ g_warn_if_reached ();
++ buffer_queue_latency_frames = 2;
++ break;
++ }
++
++ max_swap_to_flip_us -= refresh_interval_us * buffer_queue_latency_frames;
++ if (max_swap_to_flip_us < 0)
++ max_swap_to_flip_us = 0;
++
+ /* Max render time shows how early the frame clock needs to be dispatched
+ * to make it to the predicted next presentation time. It is composed of:
+ * - An estimate of duration from dispatch start to buffer swap.
+@@ -392,8 +444,6 @@ 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);
+-
+ return max_render_time_us;
+ }
+
+@@ -563,8 +613,12 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock)
+ frame_clock->pending_reschedule = 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:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ break;
+ }
+
+@@ -600,11 +654,17 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ next_update_time_us = g_get_monotonic_time ();
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ return;
+- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
+- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
++ next_update_time_us = g_get_monotonic_time ();
++ frame_clock->state =
++ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED;
++ break;
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
+ frame_clock->pending_reschedule = TRUE;
+ frame_clock->pending_reschedule_now = TRUE;
+ return;
+@@ -613,7 +673,6 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
+ g_warn_if_fail (next_update_time_us != -1);
+
+ g_source_set_ready_time (frame_clock->source, next_update_time_us);
+- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ frame_clock->is_next_presentation_time_valid = FALSE;
+ }
+
+@@ -632,6 +691,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
+ {
+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
+ next_update_time_us = g_get_monotonic_time ();
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
+ calculate_next_update_time_us (frame_clock,
+@@ -639,11 +699,28 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
+ &frame_clock->next_presentation_time_us);
+ frame_clock->is_next_presentation_time_valid =
+ (frame_clock->next_presentation_time_us != 0);
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ break;
+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
+ return;
+- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
+- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
++ if (frame_clock->last_flip_hints & CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED ||
++ triple_buffering_disabled)
++ {
++ /* 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);
++ 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;
+ }
+@@ -651,7 +728,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
+ g_warn_if_fail (next_update_time_us != -1);
+
+ g_source_set_ready_time (frame_clock->source, next_update_time_us);
+- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
+ }
+
+ static void
+@@ -677,7 +753,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;
+@@ -685,7 +761,21 @@ 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;
++ 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:
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
++ break;
++ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO;
++ break;
++ }
+
+ frame_count = frame_clock->frame_count++;
+
+@@ -710,25 +800,31 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
+ 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:
+- 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:
++ 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_TWO:
++ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE;
++ maybe_reschedule_update (frame_clock);
++ break;
+ }
+ break;
+ }
+@@ -761,10 +857,12 @@ 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->last_flip_time_us = flip_time_us;
++ frame_clock->last_flip_hints = hints;
+ }
+
+ GString *
+@@ -895,6 +993,9 @@ clutter_frame_clock_class_init (ClutterFrameClockClass *klass)
+ {
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
++ if (!g_strcmp0 (g_getenv ("MUTTER_DEBUG_DISABLE_TRIPLE_BUFFERING"), "1"))
++ triple_buffering_disabled = TRUE;
++
+ object_class->dispose = clutter_frame_clock_dispose;
+
+ signals[DESTROY] =
+diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h
+index 91e6b3a13039717807b0ebb487afbf49867915eb..d750404d5342bdaba315d2e5dfeba988a6ef1ca0 100644
+--- a/clutter/clutter/clutter-frame-clock.h
++++ b/clutter/clutter/clutter-frame-clock.h
+@@ -34,6 +34,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,
+@@ -90,8 +96,9 @@ 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 e0088564fcd95f5cfb5d807fe4568805ddf8adb4..06581492f58c30f5d4e0ad7b7794e63f61fc614e 100644
+--- a/clutter/clutter/clutter-frame-private.h
++++ b/clutter/clutter/clutter-frame-private.h
+@@ -24,6 +24,7 @@ struct _ClutterFrame
+ {
+ gboolean has_result;
+ ClutterFrameResult result;
++ ClutterFrameHint hints;
+ };
+
+ #define CLUTTER_FRAME_INIT ((ClutterFrame) { 0 })
+diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c
+index 3c708da9dc8530a99fa9795e438dce3dc3517543..63ae302af7aaf8fd4b5bd357ac0ed5c2ae5da3cc 100644
+--- a/clutter/clutter/clutter-frame.c
++++ b/clutter/clutter/clutter-frame.c
+@@ -40,3 +40,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 d3608e81ca71dfc5acdccc0111dd0f032025e0d0..06c5f7f28a58d421994036036a2f67d92ce53221 100644
+--- a/clutter/clutter/clutter-frame.h
++++ b/clutter/clutter/clutter-frame.h
+@@ -33,4 +33,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);
++
+ #endif /* CLUTTER_FRAME_H */
+diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
+index aa98e6a629755ceec86eb2053399911e0c62c4c1..fada3a6103cd7c7506037788365042bdd0b2883f 100644
+--- a/clutter/clutter/clutter-stage-view.c
++++ b/clutter/clutter/clutter-stage-view.c
+@@ -1252,8 +1252,9 @@ 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);
+
+diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h
+index dffe018d2ed7828ae539edcca0cf2396795261dd..e0215f750d861e832b7dd1e78ec99014d37ba785 100644
+--- a/cogl/cogl/cogl-onscreen-private.h
++++ b/cogl/cogl/cogl-onscreen-private.h
+@@ -97,4 +97,7 @@ cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen);
+ COGL_EXPORT CoglFrameInfo *
+ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen);
+
++COGL_EXPORT unsigned int
++cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen);
++
+ #endif /* __COGL_ONSCREEN_PRIVATE_H */
+diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c
+index ff9e1749a7dd6024f03ed33b42c060b5e9f3eef5..b25b4af4ab7cc2d22fe52463024630657cc8e398 100644
+--- a/cogl/cogl/cogl-onscreen.c
++++ b/cogl/cogl/cogl-onscreen.c
+@@ -510,6 +510,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen)
+ return g_queue_pop_head (&priv->pending_frame_infos);
+ }
+
++unsigned int
++cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen)
++{
++ CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen);
++
++ return g_queue_get_length (&priv->pending_frame_infos);
++}
++
+ 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 ca6f1f44f47a0bf531be440413b94c4f3ce407a6..a4b1c8cd0c0225f181cff41e9b3f35a8d7f7e412 100644
+--- a/src/backends/meta-stage-impl.c
++++ b/src/backends/meta-stage-impl.c
+@@ -721,6 +721,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-crtc-kms.c b/src/backends/native/meta-crtc-kms.c
+index 0bfd3f5ad5fe01ac808854185402d7b2c38970ac..3590608ee08ec5ed764bec256fbe74184a70294e 100644
+--- a/src/backends/native/meta-crtc-kms.c
++++ b/src/backends/native/meta-crtc-kms.c
+@@ -394,7 +394,7 @@ meta_crtc_kms_maybe_set_gamma (MetaCrtcKms *crtc_kms,
+ if (!gamma)
+ return;
+
+- kms_update = meta_kms_ensure_pending_update (kms, kms_device);
++ kms_update = meta_kms_ensure_pending_update_for_crtc (kms, kms_crtc);
+ meta_kms_update_set_crtc_gamma (kms_update,
+ kms_crtc,
+ gamma->size,
+diff --git a/src/backends/native/meta-cursor-renderer-native.c b/src/backends/native/meta-cursor-renderer-native.c
+index f4b42c56c1ad86df5529dadb792bb0badeaaecac..81ce0c6278d800e3adfdb2dfd0fde6a8b92871fd 100644
+--- a/src/backends/native/meta-cursor-renderer-native.c
++++ b/src/backends/native/meta-cursor-renderer-native.c
+@@ -58,19 +58,6 @@
+ #include "wayland/meta-wayland-buffer.h"
+ #endif
+
+-/* When animating a cursor, we usually call drmModeSetCursor2 once per frame.
+- * Though, testing shows that we need to triple buffer the cursor buffer in
+- * order to avoid glitches when animating the cursor, at least when running on
+- * Intel. The reason for this might be (but is not confirmed to be) due to
+- * the user space gbm_bo cache, making us reuse and overwrite the kernel side
+- * buffer content before it was scanned out. To avoid this, we keep a user space
+- * reference to each buffer we set until at least one frame after it was drawn.
+- * In effect, this means we three active cursor gbm_bo's: one that that just has
+- * been set, one that was previously set and may or may not have been scanned
+- * out, and one pending that will be replaced if the cursor sprite changes.
+- */
+-#define HW_CURSOR_BUFFER_COUNT 3
+-
+ static GQuark quark_cursor_sprite = 0;
+
+ typedef struct _CrtcCursorData
+@@ -104,19 +91,10 @@ typedef struct _MetaCursorRendererNativeGpuData
+ uint64_t cursor_height;
+ } MetaCursorRendererNativeGpuData;
+
+-typedef enum _MetaCursorBufferState
+-{
+- META_CURSOR_BUFFER_STATE_NONE,
+- META_CURSOR_BUFFER_STATE_SET,
+- META_CURSOR_BUFFER_STATE_INVALIDATED,
+-} MetaCursorBufferState;
+-
+ typedef struct _MetaCursorNativeGpuState
+ {
+ MetaGpu *gpu;
+- unsigned int active_buffer_idx;
+- MetaCursorBufferState pending_buffer_state;
+- MetaDrmBuffer *buffers[HW_CURSOR_BUFFER_COUNT];
++ MetaDrmBuffer *buffer;
+ } MetaCursorNativeGpuState;
+
+ typedef struct _MetaCursorNativePrivate
+@@ -197,44 +175,17 @@ meta_cursor_renderer_native_finalize (GObject *object)
+ G_OBJECT_CLASS (meta_cursor_renderer_native_parent_class)->finalize (object);
+ }
+
+-static unsigned int
+-get_pending_cursor_sprite_buffer_index (MetaCursorNativeGpuState *cursor_gpu_state)
+-{
+- return (cursor_gpu_state->active_buffer_idx + 1) % HW_CURSOR_BUFFER_COUNT;
+-}
+-
+-static MetaDrmBuffer *
+-get_pending_cursor_sprite_buffer (MetaCursorNativeGpuState *cursor_gpu_state)
+-{
+- unsigned int pending_buffer_idx;
+-
+- pending_buffer_idx =
+- get_pending_cursor_sprite_buffer_index (cursor_gpu_state);
+- return cursor_gpu_state->buffers[pending_buffer_idx];
+-}
+-
+-static MetaDrmBuffer *
+-get_active_cursor_sprite_buffer (MetaCursorNativeGpuState *cursor_gpu_state)
+-{
+- return cursor_gpu_state->buffers[cursor_gpu_state->active_buffer_idx];
+-}
+-
+ static void
+-set_pending_cursor_sprite_buffer (MetaCursorSprite *cursor_sprite,
+- MetaGpuKms *gpu_kms,
+- MetaDrmBuffer *buffer)
++set_cursor_sprite_buffer (MetaCursorSprite *cursor_sprite,
++ MetaGpuKms *gpu_kms,
++ MetaDrmBuffer *buffer)
+ {
+ MetaCursorNativePrivate *cursor_priv;
+ MetaCursorNativeGpuState *cursor_gpu_state;
+- unsigned int pending_buffer_idx;
+
+ cursor_priv = ensure_cursor_priv (cursor_sprite);
+ cursor_gpu_state = ensure_cursor_gpu_state (cursor_priv, gpu_kms);
+-
+- pending_buffer_idx =
+- get_pending_cursor_sprite_buffer_index (cursor_gpu_state);
+- cursor_gpu_state->buffers[pending_buffer_idx] = buffer;
+- cursor_gpu_state->pending_buffer_state = META_CURSOR_BUFFER_STATE_SET;
++ cursor_gpu_state->buffer = buffer;
+ }
+
+ static void
+@@ -309,10 +260,7 @@ assign_cursor_plane (MetaCursorRendererNative *native,
+ MetaKmsUpdate *kms_update;
+ MetaKmsPlaneAssignment *plane_assignment;
+
+- if (cursor_gpu_state->pending_buffer_state == META_CURSOR_BUFFER_STATE_SET)
+- buffer = get_pending_cursor_sprite_buffer (cursor_gpu_state);
+- else
+- buffer = get_active_cursor_sprite_buffer (cursor_gpu_state);
++ buffer = cursor_gpu_state->buffer;
+
+ kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
+ kms_device = meta_kms_crtc_get_device (kms_crtc);
+@@ -341,8 +289,8 @@ assign_cursor_plane (MetaCursorRendererNative *native,
+ flags |= META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED;
+
+ kms_update =
+- meta_kms_ensure_pending_update (meta_kms_device_get_kms (kms_device),
+- meta_kms_crtc_get_device (kms_crtc));
++ meta_kms_ensure_pending_update_for_crtc (meta_kms_device_get_kms (kms_device),
++ kms_crtc);
+ plane_assignment = meta_kms_update_assign_plane (kms_update,
+ kms_crtc,
+ cursor_plane,
+@@ -363,13 +311,6 @@ assign_cursor_plane (MetaCursorRendererNative *native,
+ native);
+
+ crtc_cursor_data->buffer = buffer;
+-
+- if (cursor_gpu_state->pending_buffer_state == META_CURSOR_BUFFER_STATE_SET)
+- {
+- cursor_gpu_state->active_buffer_idx =
+- (cursor_gpu_state->active_buffer_idx + 1) % HW_CURSOR_BUFFER_COUNT;
+- cursor_gpu_state->pending_buffer_state = META_CURSOR_BUFFER_STATE_NONE;
+- }
+ }
+
+ static float
+@@ -494,7 +435,7 @@ unset_crtc_cursor (MetaCursorRendererNative *native,
+ MetaKms *kms = meta_kms_device_get_kms (kms_device);
+ MetaKmsUpdate *kms_update;
+
+- kms_update = meta_kms_ensure_pending_update (kms, kms_device);
++ kms_update = meta_kms_ensure_pending_update_for_crtc (kms, kms_crtc);
+ meta_kms_update_unassign_plane (kms_update, kms_crtc, cursor_plane);
+ }
+
+@@ -602,19 +543,7 @@ has_valid_cursor_sprite_buffer (MetaCursorSprite *cursor_sprite,
+ if (!cursor_gpu_state)
+ return FALSE;
+
+- switch (cursor_gpu_state->pending_buffer_state)
+- {
+- case META_CURSOR_BUFFER_STATE_NONE:
+- return get_active_cursor_sprite_buffer (cursor_gpu_state) != NULL;
+- case META_CURSOR_BUFFER_STATE_SET:
+- return TRUE;
+- case META_CURSOR_BUFFER_STATE_INVALIDATED:
+- return FALSE;
+- }
+-
+- g_assert_not_reached ();
+-
+- return FALSE;
++ return cursor_gpu_state->buffer != NULL;
+ }
+
+ static void
+@@ -1119,16 +1048,14 @@ unset_crtc_cursor_renderer_privates (MetaGpu *gpu,
+ static void
+ cursor_gpu_state_free (MetaCursorNativeGpuState *cursor_gpu_state)
+ {
+- int i;
+ MetaDrmBuffer *active_buffer;
+
+- active_buffer = get_active_cursor_sprite_buffer (cursor_gpu_state);
++ active_buffer = cursor_gpu_state->buffer;
+ if (active_buffer)
+ unset_crtc_cursor_renderer_privates (cursor_gpu_state->gpu,
+ active_buffer);
+
+- for (i = 0; i < HW_CURSOR_BUFFER_COUNT; i++)
+- g_clear_object (&cursor_gpu_state->buffers[i]);
++ g_clear_object (&cursor_gpu_state->buffer);
+ g_free (cursor_gpu_state);
+ }
+
+@@ -1165,14 +1092,7 @@ invalidate_cursor_gpu_state (MetaCursorSprite *cursor_sprite)
+
+ g_hash_table_iter_init (&iter, cursor_priv->gpu_states);
+ while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &cursor_gpu_state))
+- {
+- unsigned int pending_buffer_idx;
+-
+- pending_buffer_idx = get_pending_cursor_sprite_buffer_index (cursor_gpu_state);
+- g_clear_object (&cursor_gpu_state->buffers[pending_buffer_idx]);
+- cursor_gpu_state->pending_buffer_state =
+- META_CURSOR_BUFFER_STATE_INVALIDATED;
+- }
++ g_clear_object (&cursor_gpu_state->buffer);
+ }
+
+ static void
+@@ -1410,35 +1330,7 @@ load_cursor_sprite_gbm_buffer_for_gpu (MetaCursorRendererNative *native,
+ return;
+ }
+
+- set_pending_cursor_sprite_buffer (cursor_sprite, gpu_kms, buffer);
+-}
+-
+-static gboolean
+-is_cursor_hw_state_valid (MetaCursorSprite *cursor_sprite,
+- MetaGpuKms *gpu_kms)
+-{
+- MetaCursorNativePrivate *cursor_priv;
+- MetaCursorNativeGpuState *cursor_gpu_state;
+-
+- cursor_priv = get_cursor_priv (cursor_sprite);
+- if (!cursor_priv)
+- return FALSE;
+-
+- cursor_gpu_state = get_cursor_gpu_state (cursor_priv, gpu_kms);
+- if (!cursor_gpu_state)
+- return FALSE;
+-
+- switch (cursor_gpu_state->pending_buffer_state)
+- {
+- case META_CURSOR_BUFFER_STATE_SET:
+- case META_CURSOR_BUFFER_STATE_NONE:
+- return TRUE;
+- case META_CURSOR_BUFFER_STATE_INVALIDATED:
+- return FALSE;
+- }
+-
+- g_assert_not_reached ();
+- return FALSE;
++ set_cursor_sprite_buffer (cursor_sprite, gpu_kms, buffer);
+ }
+
+ static gboolean
+@@ -1605,7 +1497,7 @@ realize_cursor_sprite_from_wl_buffer_for_gpu (MetaCursorRenderer *renderer,
+ if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
+ return;
+
+- if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
++ if (has_valid_cursor_sprite_buffer (cursor_sprite, gpu_kms) &&
+ is_cursor_scale_and_transform_valid (renderer, cursor_sprite))
+ return;
+
+@@ -1750,8 +1642,8 @@ realize_cursor_sprite_from_wl_buffer_for_gpu (MetaCursorRenderer *renderer,
+ return;
+ }
+
+- set_pending_cursor_sprite_buffer (cursor_sprite, gpu_kms,
+- META_DRM_BUFFER (buffer_gbm));
++ set_cursor_sprite_buffer (cursor_sprite, gpu_kms,
++ META_DRM_BUFFER (buffer_gbm));
+ }
+ }
+ #endif
+@@ -1775,7 +1667,7 @@ realize_cursor_sprite_from_xcursor_for_gpu (MetaCursorRenderer *renderer,
+ if (!cursor_renderer_gpu_data || cursor_renderer_gpu_data->hw_cursor_broken)
+ return;
+
+- if (is_cursor_hw_state_valid (cursor_sprite, gpu_kms) &&
++ if (has_valid_cursor_sprite_buffer (cursor_sprite, gpu_kms) &&
+ is_cursor_scale_and_transform_valid (renderer, cursor_sprite))
+ return;
+
+diff --git a/src/backends/native/meta-kms-crtc.c b/src/backends/native/meta-kms-crtc.c
+index ee9e19d2d99ae638347bc2ccdea66ecb83fb0823..0da9a77853f14a7fd606e29aa00e7d9ee5f57289 100644
+--- a/src/backends/native/meta-kms-crtc.c
++++ b/src/backends/native/meta-kms-crtc.c
+@@ -32,6 +32,12 @@ typedef struct _MetaKmsCrtcPropTable
+ MetaKmsProp props[META_KMS_CRTC_N_PROPS];
+ } MetaKmsCrtcPropTable;
+
++typedef struct
++{
++ MetaDrmBuffer *front, *back;
++ gboolean back_is_set;
++} PlaneState;
++
+ struct _MetaKmsCrtc
+ {
+ GObject parent;
+@@ -44,6 +50,8 @@ struct _MetaKmsCrtc
+ MetaKmsCrtcState current_state;
+
+ MetaKmsCrtcPropTable prop_table;
++
++ GHashTable *plane_states;
+ };
+
+ G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT)
+@@ -403,20 +411,91 @@ meta_kms_crtc_new (MetaKmsImplDevice *impl_device,
+ return crtc;
+ }
+
++void
++meta_kms_crtc_remember_plane_buffer (MetaKmsCrtc *crtc,
++ uint32_t plane_id,
++ MetaDrmBuffer *buffer)
++{
++ gpointer key = GUINT_TO_POINTER (plane_id);
++ PlaneState *plane_state;
++
++ plane_state = g_hash_table_lookup (crtc->plane_states, key);
++ if (plane_state == NULL)
++ {
++ plane_state = g_new0 (PlaneState, 1);
++ g_hash_table_insert (crtc->plane_states, key, plane_state);
++ }
++
++ plane_state->back_is_set = TRUE; /* note buffer may be NULL */
++ g_set_object (&plane_state->back, buffer);
++}
++
++static void
++swap_plane_buffers (gpointer key,
++ gpointer value,
++ gpointer user_data)
++{
++ PlaneState *plane_state = value;
++
++ if (plane_state->back_is_set)
++ {
++ g_set_object (&plane_state->front, plane_state->back);
++ g_clear_object (&plane_state->back);
++ plane_state->back_is_set = FALSE;
++ }
++}
++
++void
++meta_kms_crtc_on_scanout_started (MetaKmsCrtc *crtc)
++{
++ g_hash_table_foreach (crtc->plane_states, swap_plane_buffers, NULL);
++}
++
++void
++meta_kms_crtc_release_buffers (MetaKmsCrtc *crtc)
++{
++ g_hash_table_remove_all (crtc->plane_states);
++}
++
++static void
++meta_kms_crtc_dispose (GObject *object)
++{
++ MetaKmsCrtc *crtc = META_KMS_CRTC (object);
++
++ meta_kms_crtc_release_buffers (crtc);
++
++ G_OBJECT_CLASS (meta_kms_crtc_parent_class)->dispose (object);
++}
++
+ static void
+ meta_kms_crtc_finalize (GObject *object)
+ {
+ MetaKmsCrtc *crtc = META_KMS_CRTC (object);
+
+ clear_gamma_state (&crtc->current_state);
++ g_hash_table_unref (crtc->plane_states);
+
+ G_OBJECT_CLASS (meta_kms_crtc_parent_class)->finalize (object);
+ }
+
++static void
++destroy_plane_state (gpointer data)
++{
++ PlaneState *plane_state = data;
++
++ g_clear_object (&plane_state->front);
++ g_clear_object (&plane_state->back);
++ g_free (plane_state);
++}
++
+ static void
+ meta_kms_crtc_init (MetaKmsCrtc *crtc)
+ {
+ crtc->current_state.gamma.size = 0;
++ crtc->plane_states = g_hash_table_new_full (NULL,
++ NULL,
++ NULL,
++ destroy_plane_state);
+ }
+
+ static void
+@@ -424,5 +503,6 @@ meta_kms_crtc_class_init (MetaKmsCrtcClass *klass)
+ {
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
++ object_class->dispose = meta_kms_crtc_dispose;
+ object_class->finalize = meta_kms_crtc_finalize;
+ }
+diff --git a/src/backends/native/meta-kms-crtc.h b/src/backends/native/meta-kms-crtc.h
+index 54801dd96ad4f8e875e79604940bcd1bc2762429..deafeb61e0e548e01604854cbef8e0f5fe77ca99 100644
+--- a/src/backends/native/meta-kms-crtc.h
++++ b/src/backends/native/meta-kms-crtc.h
+@@ -25,6 +25,7 @@
+ #include <xf86drmMode.h>
+
+ #include "backends/native/meta-kms-types.h"
++#include "backends/native/meta-drm-buffer.h"
+ #include "core/util-private.h"
+ #include "meta/boxes.h"
+
+@@ -84,4 +85,12 @@ MetaKmsCrtcGamma * meta_kms_crtc_gamma_new (MetaKmsCrtc *crtc,
+ const uint16_t *green,
+ const uint16_t *blue);
+
++void meta_kms_crtc_remember_plane_buffer (MetaKmsCrtc *crtc,
++ uint32_t plane_id,
++ MetaDrmBuffer *buffer);
++
++void meta_kms_crtc_on_scanout_started (MetaKmsCrtc *crtc);
++
++void meta_kms_crtc_release_buffers (MetaKmsCrtc *crtc);
++
+ #endif /* META_KMS_CRTC_H */
+diff --git a/src/backends/native/meta-kms-impl-device-atomic.c b/src/backends/native/meta-kms-impl-device-atomic.c
+index e938e4522c865251544e50748cecdc6f1d214bce..5271cbecf4ff96ab889eec99be51620de55a66ee 100644
+--- a/src/backends/native/meta-kms-impl-device-atomic.c
++++ b/src/backends/native/meta-kms-impl-device-atomic.c
+@@ -457,6 +457,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
+ {
+ MetaKmsPlaneAssignment *plane_assignment = update_entry;
+ MetaKmsPlane *plane = plane_assignment->plane;
++ MetaKmsUpdateFlag flags = (MetaKmsUpdateFlag) user_data;
+ MetaDrmBuffer *buffer;
+ MetaKmsFbDamage *fb_damage;
+ uint32_t prop_id;
+@@ -609,6 +610,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
+ error))
+ return FALSE;
+ }
++
++ if (!(flags & META_KMS_UPDATE_FLAG_TEST_ONLY))
++ meta_kms_crtc_remember_plane_buffer (plane_assignment->crtc,
++ meta_kms_plane_get_id (plane),
++ buffer);
++
+ return TRUE;
+ }
+
+@@ -986,7 +993,7 @@ meta_kms_impl_device_atomic_process_update (MetaKmsImplDevice *impl_device,
+ req,
+ blob_ids,
+ meta_kms_update_get_plane_assignments (update),
+- NULL,
++ GUINT_TO_POINTER (flags),
+ process_plane_assignment,
+ &error))
+ goto err;
+diff --git a/src/backends/native/meta-kms-impl-device-simple.c b/src/backends/native/meta-kms-impl-device-simple.c
+index 648de51c65063bd248ce4f100b8182bbddbde27f..53d76291adca22e668d3c240a02c5f59f4925eee 100644
+--- a/src/backends/native/meta-kms-impl-device-simple.c
++++ b/src/backends/native/meta-kms-impl-device-simple.c
+@@ -486,6 +486,8 @@ process_mode_set (MetaKmsImplDevice *impl_device,
+ return FALSE;
+ }
+
++ meta_kms_crtc_on_scanout_started (crtc);
++
+ if (drm_mode)
+ {
+ g_hash_table_replace (impl_device_simple->cached_mode_sets,
+@@ -550,7 +552,7 @@ is_timestamp_earlier_than (uint64_t ts1,
+ typedef struct _RetryPageFlipData
+ {
+ MetaKmsCrtc *crtc;
+- uint32_t fb_id;
++ MetaDrmBuffer *fb;
+ MetaKmsPageFlipData *page_flip_data;
+ float refresh_rate;
+ uint64_t retry_time_us;
+@@ -563,6 +565,7 @@ retry_page_flip_data_free (RetryPageFlipData *retry_page_flip_data)
+ g_assert (!retry_page_flip_data->page_flip_data);
+ g_clear_pointer (&retry_page_flip_data->custom_page_flip,
+ meta_kms_custom_page_flip_free);
++ g_clear_object (&retry_page_flip_data->fb);
+ g_free (retry_page_flip_data);
+ }
+
+@@ -630,16 +633,21 @@ retry_page_flips (gpointer user_data)
+ }
+ else
+ {
++ uint32_t fb_id =
++ retry_page_flip_data->fb ?
++ meta_drm_buffer_get_fb_id (retry_page_flip_data->fb) :
++ 0;
++
+ meta_topic (META_DEBUG_KMS,
+ "[simple] Retrying page flip on CRTC %u (%s) with %u",
+ meta_kms_crtc_get_id (crtc),
+ meta_kms_impl_device_get_path (impl_device),
+- retry_page_flip_data->fb_id);
++ fb_id);
+
+ fd = meta_kms_impl_device_get_fd (impl_device);
+ ret = drmModePageFlip (fd,
+ meta_kms_crtc_get_id (crtc),
+- retry_page_flip_data->fb_id,
++ fb_id,
+ DRM_MODE_PAGE_FLIP_EVENT,
+ retry_page_flip_data->page_flip_data);
+ }
+@@ -726,7 +734,7 @@ retry_page_flips (gpointer user_data)
+ static void
+ schedule_retry_page_flip (MetaKmsImplDeviceSimple *impl_device_simple,
+ MetaKmsCrtc *crtc,
+- uint32_t fb_id,
++ MetaDrmBuffer *fb,
+ float refresh_rate,
+ MetaKmsPageFlipData *page_flip_data,
+ MetaKmsCustomPageFlip *custom_page_flip)
+@@ -741,7 +749,7 @@ schedule_retry_page_flip (MetaKmsImplDeviceSimple *impl_device_simple,
+ retry_page_flip_data = g_new0 (RetryPageFlipData, 1);
+ *retry_page_flip_data = (RetryPageFlipData) {
+ .crtc = crtc,
+- .fb_id = fb_id,
++ .fb = fb ? g_object_ref (fb) : NULL,
+ .page_flip_data = page_flip_data,
+ .refresh_rate = refresh_rate,
+ .retry_time_us = retry_time_us,
+@@ -875,6 +883,8 @@ mode_set_fallback (MetaKmsImplDeviceSimple *impl_device_simple,
+ return FALSE;
+ }
+
++ meta_kms_crtc_on_scanout_started (crtc);
++
+ if (!impl_device_simple->mode_set_fallback_feedback_source)
+ {
+ GSource *source;
+@@ -999,20 +1009,20 @@ dispatch_page_flip (MetaKmsImplDevice *impl_device,
+ cached_mode_set = get_cached_mode_set (impl_device_simple, crtc);
+ if (cached_mode_set)
+ {
+- uint32_t fb_id;
++ MetaDrmBuffer *fb;
+ drmModeModeInfo *drm_mode;
+ float refresh_rate;
+
+ if (plane_assignment)
+- fb_id = meta_drm_buffer_get_fb_id (plane_assignment->buffer);
++ fb = plane_assignment->buffer;
+ else
+- fb_id = 0;
++ fb = NULL;
+ drm_mode = cached_mode_set->drm_mode;
+ refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode);
+ meta_kms_impl_device_hold_fd (impl_device);
+ schedule_retry_page_flip (impl_device_simple,
+ crtc,
+- fb_id,
++ fb,
+ refresh_rate,
+ page_flip_data,
+ g_steal_pointer (&custom_page_flip));
+@@ -1302,7 +1312,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
+ {
+ case META_KMS_PLANE_TYPE_PRIMARY:
+ /* Handled as part of the mode-set and page flip. */
+- return TRUE;
++ goto assigned;
+ case META_KMS_PLANE_TYPE_CURSOR:
+ if (!process_cursor_plane_assignment (impl_device, update,
+ plane_assignment,
+@@ -1316,7 +1326,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
+ }
+ else
+ {
+- return TRUE;
++ goto assigned;
+ }
+ case META_KMS_PLANE_TYPE_OVERLAY:
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED,
+@@ -1329,6 +1339,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
+ }
+
+ g_assert_not_reached ();
++
++assigned:
++ meta_kms_crtc_remember_plane_buffer (plane_assignment->crtc,
++ meta_kms_plane_get_id (plane),
++ plane_assignment->buffer);
++ return TRUE;
+ }
+
+ static gboolean
+diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c
+index b1dd321a7f1a9c0b9cfad5e17a4666a7df50822c..c42320a84dca1c3c03027f1ca624cc14495ca696 100644
+--- a/src/backends/native/meta-kms-impl-device.c
++++ b/src/backends/native/meta-kms-impl-device.c
+@@ -1206,8 +1206,12 @@ meta_kms_impl_device_init_mode_setting (MetaKmsImplDevice *impl_device,
+ void
+ meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device)
+ {
++ MetaKmsImplDevicePrivate *priv =
++ meta_kms_impl_device_get_instance_private (impl_device);
+ MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
+
++ g_list_foreach (priv->crtcs, (GFunc) meta_kms_crtc_release_buffers, NULL);
++
+ if (klass->prepare_shutdown)
+ klass->prepare_shutdown (impl_device);
+
+diff --git a/src/backends/native/meta-kms-page-flip.c b/src/backends/native/meta-kms-page-flip.c
+index c1c29905c6256159ffbc12d574dff4de418a5a74..a6fbaf2f88ca01070dd8dbb0278441376b66c87e 100644
+--- a/src/backends/native/meta-kms-page-flip.c
++++ b/src/backends/native/meta-kms-page-flip.c
+@@ -25,6 +25,7 @@
+ #include "backends/native/meta-kms-impl.h"
+ #include "backends/native/meta-kms-private.h"
+ #include "backends/native/meta-kms-update.h"
++#include "backends/native/meta-kms-crtc.h"
+
+ typedef struct _MetaKmsPageFlipClosure
+ {
+@@ -150,6 +151,8 @@ meta_kms_page_flip_data_flipped (MetaKms *kms,
+
+ meta_assert_not_in_kms_impl (kms);
+
++ meta_kms_crtc_on_scanout_started (page_flip_data->crtc);
++
+ for (l = page_flip_data->closures; l; l = l->next)
+ {
+ MetaKmsPageFlipClosure *closure = l->data;
+diff --git a/src/backends/native/meta-kms-update-private.h b/src/backends/native/meta-kms-update-private.h
+index 3a648ba0da60488d6d358118ce50b89e8e1c6d8f..cfcf4b0505d5c9c7eef2ed226a79e6454d5a5f03 100644
+--- a/src/backends/native/meta-kms-update-private.h
++++ b/src/backends/native/meta-kms-update-private.h
+@@ -137,6 +137,12 @@ uint64_t meta_kms_update_get_sequence_number (MetaKmsUpdate *update);
+ META_EXPORT_TEST
+ MetaKmsDevice * meta_kms_update_get_device (MetaKmsUpdate *update);
+
++gboolean meta_kms_update_includes_crtc (MetaKmsUpdate *update,
++ MetaKmsCrtc *crtc);
++
++void meta_kms_update_include_crtc (MetaKmsUpdate *update,
++ MetaKmsCrtc *crtc);
++
+ void meta_kms_plane_assignment_set_rotation (MetaKmsPlaneAssignment *plane_assignment,
+ MetaKmsPlaneRotation rotation);
+
+diff --git a/src/backends/native/meta-kms-update.c b/src/backends/native/meta-kms-update.c
+index 4c34000ef5d562e28b76a1084901ef3e8cf721b7..cbbf1a0dabe0c3ea2ae453a515f3b381d114bf61 100644
+--- a/src/backends/native/meta-kms-update.c
++++ b/src/backends/native/meta-kms-update.c
+@@ -25,12 +25,14 @@
+ #include "backends/meta-display-config-shared.h"
+ #include "backends/native/meta-kms-connector.h"
+ #include "backends/native/meta-kms-crtc.h"
++#include "backends/native/meta-kms-device.h"
+ #include "backends/native/meta-kms-mode-private.h"
+ #include "backends/native/meta-kms-plane.h"
+
+ struct _MetaKmsUpdate
+ {
+ MetaKmsDevice *device;
++ GHashTable *crtcs;
+
+ gboolean is_locked;
+ uint64_t sequence_number;
+@@ -149,6 +151,7 @@ static void
+ meta_kms_plane_assignment_free (MetaKmsPlaneAssignment *plane_assignment)
+ {
+ g_clear_pointer (&plane_assignment->fb_damage, meta_kms_fb_damage_free);
++ g_clear_object (&plane_assignment->buffer);
+ g_free (plane_assignment);
+ }
+
+@@ -228,7 +231,7 @@ meta_kms_update_assign_plane (MetaKmsUpdate *update,
+ .update = update,
+ .crtc = crtc,
+ .plane = plane,
+- .buffer = buffer,
++ .buffer = g_object_ref (buffer),
+ .src_rect = src_rect,
+ .dst_rect = dst_rect,
+ .flags = flags,
+@@ -237,6 +240,8 @@ meta_kms_update_assign_plane (MetaKmsUpdate *update,
+ update->plane_assignments = g_list_prepend (update->plane_assignments,
+ plane_assignment);
+
++ g_hash_table_add (update->crtcs, crtc);
++
+ return plane_assignment;
+ }
+
+@@ -251,6 +256,8 @@ meta_kms_update_unassign_plane (MetaKmsUpdate *update,
+ g_assert (meta_kms_crtc_get_device (crtc) == update->device);
+ g_assert (meta_kms_plane_get_device (plane) == update->device);
+
++ drop_plane_assignment (update, plane, NULL);
++
+ plane_assignment = g_new0 (MetaKmsPlaneAssignment, 1);
+ *plane_assignment = (MetaKmsPlaneAssignment) {
+ .update = update,
+@@ -262,6 +269,8 @@ meta_kms_update_unassign_plane (MetaKmsUpdate *update,
+ update->plane_assignments = g_list_prepend (update->plane_assignments,
+ plane_assignment);
+
++ g_hash_table_add (update->crtcs, crtc);
++
+ return plane_assignment;
+ }
+
+@@ -284,6 +293,8 @@ meta_kms_update_mode_set (MetaKmsUpdate *update,
+ };
+
+ update->mode_sets = g_list_prepend (update->mode_sets, mode_set);
++
++ g_hash_table_add (update->crtcs, crtc);
+ }
+
+ static MetaKmsConnectorUpdate *
+@@ -292,6 +303,8 @@ ensure_connector_update (MetaKmsUpdate *update,
+ {
+ GList *l;
+ MetaKmsConnectorUpdate *connector_update;
++ MetaKmsDevice *device;
++ const MetaKmsConnectorState *state;
+
+ for (l = update->connector_updates; l; l = l->next)
+ {
+@@ -306,6 +319,23 @@ ensure_connector_update (MetaKmsUpdate *update,
+
+ update->connector_updates = g_list_prepend (update->connector_updates,
+ connector_update);
++ device = meta_kms_connector_get_device (connector);
++ state = meta_kms_connector_get_current_state (connector);
++ if (device && state && state->current_crtc_id)
++ {
++ GList *l;
++
++ for (l = meta_kms_device_get_crtcs (device); l; l = l->next)
++ {
++ MetaKmsCrtc *kms_crtc = l->data;
++
++ if (meta_kms_crtc_get_id (kms_crtc) == state->current_crtc_id)
++ {
++ g_hash_table_add (update->crtcs, kms_crtc);
++ break;
++ }
++ }
++ }
+
+ return connector_update;
+ }
+@@ -416,6 +446,8 @@ meta_kms_update_set_crtc_gamma (MetaKmsUpdate *update,
+ gamma = meta_kms_crtc_gamma_new (crtc, size, red, green, blue);
+
+ update->crtc_gammas = g_list_prepend (update->crtc_gammas, gamma);
++
++ g_hash_table_add (update->crtcs, crtc);
+ }
+
+ void
+@@ -679,6 +711,20 @@ meta_kms_update_get_device (MetaKmsUpdate *update)
+ return update->device;
+ }
+
++gboolean
++meta_kms_update_includes_crtc (MetaKmsUpdate *update,
++ MetaKmsCrtc *crtc)
++{
++ return g_hash_table_contains (update->crtcs, crtc);
++}
++
++void
++meta_kms_update_include_crtc (MetaKmsUpdate *update,
++ MetaKmsCrtc *crtc)
++{
++ g_hash_table_add (update->crtcs, crtc);
++}
++
+ MetaKmsCustomPageFlip *
+ meta_kms_update_take_custom_page_flip_func (MetaKmsUpdate *update)
+ {
+@@ -707,12 +753,15 @@ meta_kms_update_new (MetaKmsDevice *device)
+ update->device = device;
+ update->sequence_number = sequence_number++;
+
++ update->crtcs = g_hash_table_new (NULL, NULL);
++
+ return update;
+ }
+
+ void
+ meta_kms_update_free (MetaKmsUpdate *update)
+ {
++ g_hash_table_destroy (update->crtcs);
+ g_list_free_full (update->result_listeners,
+ (GDestroyNotify) meta_kms_result_listener_free);
+ g_list_free_full (update->plane_assignments,
+diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c
+index 21be532a2fc6060fec047a116c2cee2190118380..b8dba8d0e3f1de236bdcad4e473c104f19e2726c 100644
+--- a/src/backends/native/meta-kms.c
++++ b/src/backends/native/meta-kms.c
+@@ -23,6 +23,7 @@
+ #include "backends/native/meta-kms-private.h"
+
+ #include "backends/native/meta-backend-native.h"
++#include "backends/native/meta-kms-crtc.h"
+ #include "backends/native/meta-kms-device-private.h"
+ #include "backends/native/meta-kms-impl.h"
+ #include "backends/native/meta-kms-update-private.h"
+@@ -177,10 +178,17 @@ struct _MetaKms
+
+ GList *pending_callbacks;
+ guint callback_source_id;
++
++ gboolean shutting_down;
+ };
+
+ G_DEFINE_TYPE (MetaKms, meta_kms, G_TYPE_OBJECT)
+
++static MetaKmsFeedback *
++meta_kms_post_update_sync (MetaKms *kms,
++ MetaKmsUpdate *update,
++ MetaKmsUpdateFlag flags);
++
+ void
+ meta_kms_discard_pending_updates (MetaKms *kms)
+ {
+@@ -247,12 +255,105 @@ meta_kms_take_pending_update (MetaKms *kms,
+ return NULL;
+ }
+
++MetaKmsUpdate *
++meta_kms_ensure_pending_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc)
++{
++ MetaKmsUpdate *update;
++
++ update = meta_kms_get_pending_update_for_crtc (kms, crtc);
++ if (update == NULL)
++ {
++ update = meta_kms_update_new (meta_kms_crtc_get_device (crtc));
++ meta_kms_update_include_crtc (update, crtc);
++ meta_kms_add_pending_update (kms, update);
++ }
++
++ return update;
++}
++
++static MetaKmsUpdate *
++meta_kms_find_compatible_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc,
++ gboolean take)
++{
++ MetaKmsDevice *device;
++ MetaKmsUpdate *update;
++ GList *l;
++
++ for (l = kms->pending_updates; l; l = l->next)
++ {
++ update = l->data;
++ if (meta_kms_update_includes_crtc (update, crtc))
++ goto found;
++ }
++
++ device = meta_kms_crtc_get_device (crtc);
++
++ for (l = kms->pending_updates; l; l = l->next)
++ {
++ update = l->data;
++ if (meta_kms_update_get_device (update) == device &&
++ meta_kms_update_get_mode_sets (update))
++ goto found;
++ }
++
++ return NULL;
++
++found:
++ if (take)
++ kms->pending_updates = g_list_delete_link (kms->pending_updates, l);
++ return update;
++}
++
++MetaKmsUpdate *
++meta_kms_get_pending_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc)
++{
++ return meta_kms_find_compatible_update_for_crtc (kms, crtc, FALSE);
++}
++
++static MetaKmsUpdate *
++meta_kms_take_pending_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc)
++{
++ return meta_kms_find_compatible_update_for_crtc (kms, crtc, TRUE);
++}
++
+ MetaKmsFeedback *
+ meta_kms_post_pending_update_sync (MetaKms *kms,
+ MetaKmsDevice *device,
+ MetaKmsUpdateFlag flags)
+ {
+ MetaKmsUpdate *update;
++
++ update = meta_kms_take_pending_update (kms, device);
++ if (!update)
++ return NULL;
++
++ return meta_kms_post_update_sync (kms, update, flags);
++}
++
++MetaKmsFeedback *
++meta_kms_post_pending_update_for_crtc_sync (MetaKms *kms,
++ MetaKmsCrtc *crtc,
++ MetaKmsUpdateFlag flags)
++{
++ MetaKmsUpdate *update;
++
++ update = meta_kms_take_pending_update_for_crtc (kms, crtc);
++ if (!update)
++ return NULL;
++
++ return meta_kms_post_update_sync (kms, update, flags);
++}
++
++static MetaKmsFeedback *
++meta_kms_post_update_sync (MetaKms *kms,
++ MetaKmsUpdate *update,
++ MetaKmsUpdateFlag flags)
++{
++ MetaKmsDevice *device = meta_kms_update_get_device (update);
+ MetaKmsFeedback *feedback;
+ GList *result_listeners;
+ GList *l;
+@@ -260,10 +361,6 @@ meta_kms_post_pending_update_sync (MetaKms *kms,
+ COGL_TRACE_BEGIN_SCOPED (MetaKmsPostUpdateSync,
+ "KMS (post update)");
+
+- update = meta_kms_take_pending_update (kms, device);
+- if (!update)
+- return NULL;
+-
+ meta_kms_update_lock (update);
+
+ feedback = meta_kms_device_process_update_sync (device, update, flags);
+@@ -752,10 +849,18 @@ prepare_shutdown_in_impl (MetaKmsImpl *impl,
+ void
+ meta_kms_prepare_shutdown (MetaKms *kms)
+ {
++ kms->shutting_down = TRUE;
++
+ meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL);
+ flush_callbacks (kms);
+ }
+
++gboolean
++meta_kms_is_shutting_down (MetaKms *kms)
++{
++ return kms->shutting_down;
++}
++
+ static void
+ meta_kms_finalize (GObject *object)
+ {
+diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h
+index bd9fe5ceadc8d4d46a1eabe775a0242bffbf77bf..e8fe2e418d67be4a9f05006493387e7471a85960 100644
+--- a/src/backends/native/meta-kms.h
++++ b/src/backends/native/meta-kms.h
+@@ -39,9 +39,15 @@ void meta_kms_discard_pending_updates (MetaKms *kms);
+ MetaKmsUpdate * meta_kms_ensure_pending_update (MetaKms *kms,
+ MetaKmsDevice *device);
+
++MetaKmsUpdate * meta_kms_ensure_pending_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc);
++
+ MetaKmsUpdate * meta_kms_get_pending_update (MetaKms *kms,
+ MetaKmsDevice *device);
+
++MetaKmsUpdate * meta_kms_get_pending_update_for_crtc (MetaKms *kms,
++ MetaKmsCrtc *crtc);
++
+ MetaKmsFeedback * meta_kms_post_pending_update_sync (MetaKms *kms,
+ MetaKmsDevice *device,
+ MetaKmsUpdateFlag flags);
+@@ -49,6 +55,10 @@ MetaKmsFeedback * meta_kms_post_pending_update_sync (MetaKms *kms,
+ MetaKmsFeedback * meta_kms_post_test_update_sync (MetaKms *kms,
+ MetaKmsUpdate *update);
+
++MetaKmsFeedback * meta_kms_post_pending_update_for_crtc_sync (MetaKms *kms,
++ MetaKmsCrtc *device,
++ MetaKmsUpdateFlag flags);
++
+ void meta_kms_discard_pending_page_flips (MetaKms *kms);
+
+ void meta_kms_notify_modes_set (MetaKms *kms);
+@@ -68,6 +78,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms,
+
+ void meta_kms_prepare_shutdown (MetaKms *kms);
+
++gboolean meta_kms_is_shutting_down (MetaKms *kms);
++
+ MetaKms * meta_kms_new (MetaBackend *backend,
+ MetaKmsFlags flags,
+ GError **error);
+diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c
+index a107a99bb37ea007d67462d07765605bfa0ca7ef..6c8245f8cb47c86fa754308db701b96fbdfbc56e 100644
+--- a/src/backends/native/meta-onscreen-native.c
++++ b/src/backends/native/meta-onscreen-native.c
+@@ -67,13 +67,12 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState
+
+ struct {
+ struct gbm_surface *surface;
+- MetaDrmBuffer *current_fb;
+- MetaDrmBuffer *next_fb;
+ } gbm;
+
+ struct {
+ MetaDrmBufferDumb *current_dumb_fb;
+ MetaDrmBufferDumb *dumb_fbs[2];
++ MetaDrmBuffer *source_fbs[2];
+ } cpu;
+
+ gboolean noted_primary_gpu_copy_ok;
+@@ -94,8 +93,8 @@ struct _MetaOnscreenNative
+
+ struct {
+ struct gbm_surface *surface;
+- MetaDrmBuffer *current_fb;
+ MetaDrmBuffer *next_fb;
++ MetaDrmBuffer *stalled_fb;
+ } gbm;
+
+ #ifdef HAVE_EGL_DEVICE
+@@ -107,69 +106,25 @@ struct _MetaOnscreenNative
+ #endif
+
+ MetaRendererView *view;
++
++ unsigned int swaps_pending;
++ struct {
++ int *rectangles; /* 4 x n_rectangles */
++ int n_rectangles;
++ } next_post;
+ };
+
+ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
+ COGL_TYPE_ONSCREEN_EGL)
+
++static void
++try_post_latest_swap (CoglOnscreen *onscreen);
++
+ static gboolean
+ init_secondary_gpu_state (MetaRendererNative *renderer_native,
+ CoglOnscreen *onscreen,
+ GError **error);
+
+-static void
+-swap_secondary_drm_fb (CoglOnscreen *onscreen)
+-{
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+- MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
+-
+- secondary_gpu_state = onscreen_native->secondary_gpu_state;
+- if (!secondary_gpu_state)
+- return;
+-
+- g_set_object (&secondary_gpu_state->gbm.current_fb,
+- secondary_gpu_state->gbm.next_fb);
+- g_clear_object (&secondary_gpu_state->gbm.next_fb);
+-}
+-
+-static void
+-free_current_secondary_bo (CoglOnscreen *onscreen)
+-{
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+- MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
+-
+- secondary_gpu_state = onscreen_native->secondary_gpu_state;
+- if (!secondary_gpu_state)
+- return;
+-
+- g_clear_object (&secondary_gpu_state->gbm.current_fb);
+-}
+-
+-static void
+-free_current_bo (CoglOnscreen *onscreen)
+-{
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+-
+- g_clear_object (&onscreen_native->gbm.current_fb);
+- free_current_secondary_bo (onscreen);
+-}
+-
+-static void
+-meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen)
+-{
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+-
+- if (!onscreen_native->gbm.next_fb)
+- return;
+-
+- free_current_bo (onscreen);
+-
+- g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb);
+- g_clear_object (&onscreen_native->gbm.next_fb);
+-
+- swap_secondary_drm_fb (onscreen);
+-}
+-
+ static void
+ maybe_update_frame_info (MetaCrtc *crtc,
+ CoglFrameInfo *frame_info,
+@@ -205,7 +160,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen)
+
+ info = cogl_onscreen_pop_head_frame_info (onscreen);
+
+- g_assert (!cogl_onscreen_peek_head_frame_info (onscreen));
++ g_assert (info);
+
+ _cogl_onscreen_notify_frame_sync (onscreen, info);
+ _cogl_onscreen_notify_complete (onscreen, info);
+@@ -234,7 +189,7 @@ notify_view_crtc_presented (MetaRendererView *view,
+ maybe_update_frame_info (crtc, frame_info, time_us, flags, sequence);
+
+ meta_onscreen_native_notify_frame_complete (onscreen);
+- meta_onscreen_native_swap_drm_fb (onscreen);
++ try_post_latest_swap (onscreen);
+ }
+
+ static int64_t
+@@ -296,6 +251,7 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc,
+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
+
+ meta_onscreen_native_notify_frame_complete (onscreen);
++ try_post_latest_swap (onscreen);
+ }
+
+ static void
+@@ -345,7 +301,7 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc,
+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
+
+ meta_onscreen_native_notify_frame_complete (onscreen);
+- meta_onscreen_native_swap_drm_fb (onscreen);
++ try_post_latest_swap (onscreen);
+ }
+
+ static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = {
+@@ -406,18 +362,40 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data,
+ }
+ #endif /* HAVE_EGL_DEVICE */
+
+-void
+-meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen)
++static void
++drop_stalled_swap (CoglOnscreen *onscreen)
+ {
++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ CoglFrameInfo *frame_info;
+
+- meta_onscreen_native_swap_drm_fb (onscreen);
++ /* Remember we can't compare stalled_fb because it's not used by
++ * META_RENDERER_NATIVE_MODE_EGL_DEVICE. So we judge stalled to be whenever
++ * swaps_pending > 1.
++ */
++ if (onscreen_native->swaps_pending <= 1)
++ return;
++
++ onscreen_native->swaps_pending--;
++
++ g_clear_object (&onscreen_native->gbm.stalled_fb);
+
+ frame_info = cogl_onscreen_peek_tail_frame_info (onscreen);
+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
+ meta_onscreen_native_notify_frame_complete (onscreen);
+ }
+
++void
++meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen)
++{
++ drop_stalled_swap (onscreen);
++
++ /* If the monitor just woke up and the shell is fully idle (has nothing
++ * more to swap) then we just woke to an indefinitely black screen. Let's
++ * fix that using the last swap (which is never classified as "stalled").
++ */
++ try_post_latest_swap (onscreen);
++}
++
+ static void
+ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
+ MetaRendererView *view,
+@@ -436,8 +414,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
+ MetaKmsDevice *kms_device;
+ MetaKms *kms;
+ MetaKmsUpdate *kms_update;
+- MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state = NULL;
+- MetaDrmBuffer *buffer;
++ g_autoptr (MetaDrmBuffer) buffer = NULL;
+ MetaKmsPlaneAssignment *plane_assignment;
+
+ COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs,
+@@ -446,7 +423,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
+ gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc));
+ kms_device = meta_gpu_kms_get_kms_device (gpu_kms);
+ kms = meta_kms_device_get_kms (kms_device);
+- kms_update = meta_kms_ensure_pending_update (kms, kms_device);
++ kms_update = meta_kms_ensure_pending_update_for_crtc (kms, kms_crtc);
+
+ g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc));
+
+@@ -455,15 +432,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
+ switch (renderer_gpu_data->mode)
+ {
+ case META_RENDERER_NATIVE_MODE_GBM:
+- if (gpu_kms == render_gpu)
+- {
+- buffer = onscreen_native->gbm.next_fb;
+- }
+- else
+- {
+- secondary_gpu_state = onscreen_native->secondary_gpu_state;
+- buffer = secondary_gpu_state->gbm.next_fb;
+- }
++ buffer = g_steal_pointer (&onscreen_native->gbm.next_fb);
+
+ plane_assignment = meta_crtc_kms_assign_primary_plane (crtc_kms,
+ buffer,
+@@ -474,6 +443,11 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
+ meta_kms_plane_assignment_set_fb_damage (plane_assignment,
+ rectangles, n_rectangles);
+ }
++
++ g_object_set_data_full (G_OBJECT (buffer),
++ "gbm_surface owner",
++ g_object_ref (onscreen),
++ (GDestroyNotify) g_object_unref);
+ break;
+ case META_RENDERER_NATIVE_MODE_SURFACELESS:
+ g_assert_not_reached ();
+@@ -509,7 +483,7 @@ meta_onscreen_native_set_crtc_mode (CoglOnscreen *onscreen,
+ COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeSetCrtcModes,
+ "Onscreen (set CRTC modes)");
+
+- kms_update = meta_kms_ensure_pending_update (kms, kms_device);
++ kms_update = meta_kms_ensure_pending_update_for_crtc (kms, kms_crtc);
+
+ switch (renderer_gpu_data->mode)
+ {
+@@ -537,13 +511,41 @@ meta_onscreen_native_set_crtc_mode (CoglOnscreen *onscreen,
+ kms_update);
+ }
+
++static void
++hold_primary_gpu_fb_for_secondary_gpu_scanout (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
++ MetaDrmBuffer *primary_gpu_fb,
++ MetaDrmBuffer *secondary_gpu_fb)
++{
++ if (META_IS_DRM_BUFFER_DUMB (secondary_gpu_fb))
++ {
++ MetaDrmBufferDumb *dumb_fb = META_DRM_BUFFER_DUMB (secondary_gpu_fb);
++ int i;
++ const int n = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs);
++
++ for (i = 0; i < n; i++)
++ {
++ if (dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i])
++ {
++ g_set_object (&secondary_gpu_state->cpu.source_fbs[i],
++ primary_gpu_fb);
++ break;
++ }
++ }
++
++ g_warn_if_fail (i < n);
++ }
++}
++
+ static void
+ secondary_gpu_release_dumb (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
+ {
+ unsigned i;
+
+ for (i = 0; i < G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); i++)
+- g_clear_object (&secondary_gpu_state->cpu.dumb_fbs[i]);
++ {
++ g_clear_object (&secondary_gpu_state->cpu.dumb_fbs[i]);
++ g_clear_object (&secondary_gpu_state->cpu.source_fbs[i]);
++ }
+ }
+
+ static void
+@@ -568,8 +570,6 @@ secondary_gpu_state_free (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_sta
+ NULL);
+ }
+
+- g_clear_object (&secondary_gpu_state->gbm.current_fb);
+- g_clear_object (&secondary_gpu_state->gbm.next_fb);
+ g_clear_pointer (&secondary_gpu_state->gbm.surface, gbm_surface_destroy);
+
+ secondary_gpu_release_dumb (secondary_gpu_state);
+@@ -577,11 +577,11 @@ secondary_gpu_state_free (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_sta
+ g_free (secondary_gpu_state);
+ }
+
+-static gboolean
++static MetaDrmBuffer *
+ import_shared_framebuffer (CoglOnscreen *onscreen,
+- MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
++ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
++ MetaDrmBuffer *primary_gpu_fb)
+ {
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaRenderDevice *render_device;
+ g_autoptr (GError) error = NULL;
+ MetaDrmBuffer *imported_buffer;
+@@ -589,7 +589,7 @@ import_shared_framebuffer (CoglOnscreen *onscreen,
+ render_device = secondary_gpu_state->renderer_gpu_data->render_device;
+ imported_buffer =
+ meta_render_device_import_dma_buf (render_device,
+- onscreen_native->gbm.next_fb,
++ primary_gpu_fb,
+ &error);
+ if (!imported_buffer)
+ {
+@@ -603,16 +603,9 @@ import_shared_framebuffer (CoglOnscreen *onscreen,
+ META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE);
+ secondary_gpu_state->import_status =
+ META_SHARED_FRAMEBUFFER_IMPORT_STATUS_FAILED;
+- return FALSE;
++ return NULL;
+ }
+
+- /*
+- * next_fb may already contain a fallback buffer, so clear it only
+- * when we are sure to succeed.
+- */
+- g_clear_object (&secondary_gpu_state->gbm.next_fb);
+- secondary_gpu_state->gbm.next_fb = imported_buffer;
+-
+ if (secondary_gpu_state->import_status ==
+ META_SHARED_FRAMEBUFFER_IMPORT_STATUS_NONE)
+ {
+@@ -629,16 +622,16 @@ import_shared_framebuffer (CoglOnscreen *onscreen,
+
+ secondary_gpu_state->import_status =
+ META_SHARED_FRAMEBUFFER_IMPORT_STATUS_OK;
+- return TRUE;
++ return imported_buffer;
+ }
+
+-static void
++static MetaDrmBuffer *
+ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
+ MetaRendererNativeGpuData *renderer_gpu_data,
+- gboolean *egl_context_changed)
++ gboolean *egl_context_changed,
++ MetaDrmBuffer *primary_gpu_fb)
+ {
+- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
+ MetaEgl *egl = meta_renderer_native_get_egl (renderer_native);
+ MetaGles3 *gles3 = meta_renderer_native_get_gles3 (renderer_native);
+@@ -654,9 +647,6 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu,
+ "FB Copy (secondary GPU)");
+
+- g_warn_if_fail (secondary_gpu_state->gbm.next_fb == NULL);
+- g_clear_object (&secondary_gpu_state->gbm.next_fb);
+-
+ render_device = renderer_gpu_data->render_device;
+ egl_display = meta_render_device_get_egl_display (render_device);
+
+@@ -669,13 +659,13 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ {
+ g_warning ("Failed to make current: %s", error->message);
+ g_error_free (error);
+- return;
++ return NULL;
+ }
+
+ *egl_context_changed = TRUE;
+
+
+- buffer_gbm = META_DRM_BUFFER_GBM (onscreen_native->gbm.next_fb);
++ buffer_gbm = META_DRM_BUFFER_GBM (primary_gpu_fb);
+ bo = meta_drm_buffer_gbm_get_bo (buffer_gbm);
+ if (!meta_renderer_native_gles3_blit_shared_bo (egl,
+ gles3,
+@@ -687,7 +677,7 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ {
+ g_warning ("Failed to blit shared framebuffer: %s", error->message);
+ g_error_free (error);
+- return;
++ return NULL;
+ }
+
+ if (!meta_egl_swap_buffers (egl,
+@@ -697,7 +687,7 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ {
+ g_warning ("Failed to swap buffers: %s", error->message);
+ g_error_free (error);
+- return;
++ return NULL;
+ }
+
+ use_modifiers = meta_renderer_native_use_modifiers (renderer_native);
+@@ -717,10 +707,10 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
+ g_warning ("meta_drm_buffer_gbm_new_lock_front failed: %s",
+ error->message);
+ g_error_free (error);
+- return;
++ return NULL;
+ }
+
+- secondary_gpu_state->gbm.next_fb = META_DRM_BUFFER (buffer_gbm);
++ return META_DRM_BUFFER (buffer_gbm);
+ }
+
+ static MetaDrmBufferDumb *
+@@ -735,7 +725,7 @@ secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *seconda
+ return secondary_gpu_state->cpu.dumb_fbs[0];
+ }
+
+-static gboolean
++static MetaDrmBuffer *
+ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscreen,
+ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
+ const int *rectangles,
+@@ -761,13 +751,13 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre
+
+ if (!secondary_gpu_state ||
+ secondary_gpu_state->egl_surface == EGL_NO_SURFACE)
+- return FALSE;
++ return NULL;
+
+ primary_gpu = meta_renderer_native_get_primary_gpu (renderer_native);
+ primary_gpu_data =
+ meta_renderer_native_get_gpu_data (renderer_native, primary_gpu);
+ if (!primary_gpu_data->secondary.has_EGL_EXT_image_dma_buf_import_modifiers)
+- return FALSE;
++ return NULL;
+
+ buffer_dumb = secondary_gpu_get_next_dumb_buffer (secondary_gpu_state);
+ buffer = META_DRM_BUFFER (buffer_dumb);
+@@ -790,7 +780,7 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre
+ {
+ meta_topic (META_DEBUG_KMS,
+ "Failed to create DMA buffer: %s", error->message);
+- return FALSE;
++ return NULL;
+ }
+
+ dmabuf_fb =
+@@ -808,7 +798,7 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre
+ meta_topic (META_DEBUG_KMS,
+ "Failed to create DMA buffer for blitting: %s",
+ error->message);
+- return FALSE;
++ return NULL;
+ }
+ /* Limit the number of individual copies to 16 */
+ #define MAX_RECTS 16
+@@ -821,7 +811,7 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre
+ &error))
+ {
+ g_object_unref (dmabuf_fb);
+- return FALSE;
++ return NULL;
+ }
+ }
+ else
+@@ -838,20 +828,19 @@ copy_shared_framebuffer_primary_gpu (CoglOnscreen *onscre
+ &error))
+ {
+ g_object_unref (dmabuf_fb);
+- return FALSE;
++ return NULL;
+ }
+ }
+ }
+
+ g_object_unref (dmabuf_fb);
+
+- g_set_object (&secondary_gpu_state->gbm.next_fb, buffer);
+ secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb;
+
+- return TRUE;
++ return g_object_ref (buffer);
+ }
+
+-static void
++static MetaDrmBuffer *
+ copy_shared_framebuffer_cpu (CoglOnscreen *onscreen,
+ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
+ MetaRendererNativeGpuData *renderer_gpu_data)
+@@ -903,17 +892,19 @@ copy_shared_framebuffer_cpu (CoglOnscreen *onscreen,
+
+ cogl_object_unref (dumb_bitmap);
+
+- g_set_object (&secondary_gpu_state->gbm.next_fb, buffer);
+ secondary_gpu_state->cpu.current_dumb_fb = buffer_dumb;
++
++ return g_object_ref (buffer);
+ }
+
+-static void
++static MetaDrmBuffer *
+ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen,
+ const int *rectangles,
+ int n_rectangles)
+ {
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state;
++ MetaDrmBuffer *copy = NULL;
+
+ COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePreSwapBuffers,
+ "Onscreen (secondary gpu pre-swap-buffers)");
+@@ -939,10 +930,11 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen,
+ /* prepare fallback */
+ G_GNUC_FALLTHROUGH;
+ case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
+- if (!copy_shared_framebuffer_primary_gpu (onscreen,
+- secondary_gpu_state,
+- rectangles,
+- n_rectangles))
++ copy = copy_shared_framebuffer_primary_gpu (onscreen,
++ secondary_gpu_state,
++ rectangles,
++ n_rectangles);
++ if (!copy)
+ {
+ if (!secondary_gpu_state->noted_primary_gpu_copy_failed)
+ {
+@@ -952,9 +944,9 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen,
+ secondary_gpu_state->noted_primary_gpu_copy_failed = TRUE;
+ }
+
+- copy_shared_framebuffer_cpu (onscreen,
+- secondary_gpu_state,
+- renderer_gpu_data);
++ copy = copy_shared_framebuffer_cpu (onscreen,
++ secondary_gpu_state,
++ renderer_gpu_data);
+ }
+ else if (!secondary_gpu_state->noted_primary_gpu_copy_ok)
+ {
+@@ -966,11 +958,15 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen,
+ break;
+ }
+ }
++
++ return copy;
+ }
+
+ static void
+-update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
+- gboolean *egl_context_changed)
++update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
++ gboolean *egl_context_changed,
++ MetaDrmBuffer *primary_gpu_fb,
++ MetaDrmBuffer **secondary_gpu_fb)
+ {
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaRendererNative *renderer_native = onscreen_native->renderer_native;
+@@ -983,6 +979,7 @@ update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
+ if (secondary_gpu_state)
+ {
+ MetaRendererNativeGpuData *renderer_gpu_data;
++ g_autoptr (MetaDrmBuffer) next_fb = NULL;
+
+ renderer_gpu_data =
+ meta_renderer_native_get_gpu_data (renderer_native,
+@@ -990,23 +987,30 @@ update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen,
+ switch (renderer_gpu_data->secondary.copy_mode)
+ {
+ case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO:
+- if (import_shared_framebuffer (onscreen, secondary_gpu_state))
++ next_fb = import_shared_framebuffer (onscreen,
++ secondary_gpu_state,
++ primary_gpu_fb);
++ if (next_fb)
+ break;
+-
+- /* The fallback was prepared in pre_swap_buffers */
++ /* The fallback was prepared in pre_swap_buffers and is currently
++ * in secondary_gpu_fb.
++ */
+ renderer_gpu_data->secondary.copy_mode =
+ META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY;
+ G_GNUC_FALLTHROUGH;
+ case META_SHARED_FRAMEBUFFER_COPY_MODE_PRIMARY:
+- /* Done before eglSwapBuffers. */
++ next_fb = g_object_ref (*secondary_gpu_fb);
+ break;
+ case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU:
+- copy_shared_framebuffer_gpu (onscreen,
+- secondary_gpu_state,
+- renderer_gpu_data,
+- egl_context_changed);
++ next_fb = copy_shared_framebuffer_gpu (onscreen,
++ secondary_gpu_state,
++ renderer_gpu_data,
++ egl_context_changed,
++ primary_gpu_fb);
+ break;
+ }
++
++ g_set_object (secondary_gpu_fb, next_fb);
+ }
+ }
+
+@@ -1040,34 +1044,39 @@ 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);
+- MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend);
+- MetaMonitorManager *monitor_manager =
+- meta_backend_get_monitor_manager (backend);
+- MetaKms *kms = meta_backend_native_get_kms (backend_native);
+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
+ MetaGpuKms *render_gpu = onscreen_native->render_gpu;
+ MetaDeviceFile *render_device_file;
+ ClutterFrame *frame = user_data;
+ CoglOnscreenClass *parent_class;
+ gboolean egl_context_changed = FALSE;
+- MetaPowerSave power_save_mode;
+ g_autoptr (GError) error = NULL;
+ MetaDrmBufferFlags buffer_flags;
+ MetaDrmBufferGbm *buffer_gbm;
+- MetaKmsCrtc *kms_crtc;
+- MetaKmsDevice *kms_device;
+- MetaKmsUpdateFlag flags;
+- g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
+- const GError *feedback_error;
++ g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL;
++ g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
++ size_t rectangles_size;
+
+ COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers,
+ "Onscreen (swap-buffers)");
+
+- update_secondary_gpu_state_pre_swap_buffers (onscreen,
+- rectangles,
+- n_rectangles);
++ 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,
++ n_rectangles);
+
+ parent_class = COGL_ONSCREEN_CLASS (meta_onscreen_native_parent_class);
+ parent_class->swap_buffers_with_damage (onscreen,
+@@ -1083,9 +1092,6 @@ 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);
+- g_clear_object (&onscreen_native->gbm.next_fb);
+-
+ buffer_flags = META_DRM_BUFFER_FLAG_NONE;
+ if (!meta_renderer_native_use_modifiers (renderer_native))
+ buffer_flags |= META_DRM_BUFFER_FLAG_DISABLE_MODIFIERS;
+@@ -1103,8 +1109,7 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ return;
+ }
+
+- onscreen_native->gbm.next_fb = META_DRM_BUFFER (buffer_gbm);
+-
++ primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm));
+ break;
+ case META_RENDERER_NATIVE_MODE_SURFACELESS:
+ g_assert_not_reached ();
+@@ -1115,7 +1120,46 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ #endif
+ }
+
+- update_secondary_gpu_state_post_swap_buffers (onscreen, &egl_context_changed);
++ update_secondary_gpu_state_post_swap_buffers (onscreen,
++ &egl_context_changed,
++ primary_gpu_fb,
++ &secondary_gpu_fb);
++
++ switch (renderer_gpu_data->mode)
++ {
++ case META_RENDERER_NATIVE_MODE_GBM:
++ if (onscreen_native->gbm.next_fb != NULL)
++ {
++ g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL);
++ drop_stalled_swap (onscreen);
++ g_assert (onscreen_native->gbm.stalled_fb == NULL);
++ onscreen_native->gbm.stalled_fb =
++ g_steal_pointer (&onscreen_native->gbm.next_fb);
++ }
++
++ if (onscreen_native->secondary_gpu_state)
++ {
++ g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb);
++ hold_primary_gpu_fb_for_secondary_gpu_scanout (
++ onscreen_native->secondary_gpu_state,
++ primary_gpu_fb,
++ secondary_gpu_fb);
++ }
++ else
++ {
++ g_set_object (&onscreen_native->gbm.next_fb, primary_gpu_fb);
++ }
++ break;
++ case META_RENDERER_NATIVE_MODE_SURFACELESS:
++ break;
++#ifdef HAVE_EGL_DEVICE
++ case META_RENDERER_NATIVE_MODE_EGL_DEVICE:
++ break;
++#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
+@@ -1126,23 +1170,77 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ if (egl_context_changed)
+ _cogl_winsys_egl_ensure_current (cogl_display);
+
++ 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;
++
++ onscreen_native->swaps_pending++;
++ try_post_latest_swap (onscreen);
++}
++
++static void
++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);
++ MetaMonitorManager *monitor_manager =
++ meta_backend_get_monitor_manager (backend);
++ MetaKms *kms = meta_backend_native_get_kms (backend_native);
++ 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);
++ MetaKmsUpdateFlag flags;
++ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
++ const GError *feedback_error;
++ unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen);
++
++ if (meta_kms_is_shutting_down (kms))
++ {
++ meta_onscreen_native_discard_pending_swaps (onscreen);
++ return;
++ }
++
++ if (onscreen_native->swaps_pending == 0)
++ return;
++
++ g_assert (frames_pending >= onscreen_native->swaps_pending);
++
+ power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager);
+ if (power_save_mode == META_POWER_SAVE_ON)
+ {
++ unsigned int posts_pending;
++
++ posts_pending = frames_pending - onscreen_native->swaps_pending;
++ if (posts_pending > 0)
++ return; /* wait for the next frame notification and then try again */
++
++ drop_stalled_swap (onscreen);
++ g_return_if_fail (onscreen_native->swaps_pending > 0);
++ onscreen_native->swaps_pending--;
++
+ ensure_crtc_modes (onscreen);
+ meta_onscreen_native_flip_crtc (onscreen,
+ onscreen_native->view,
+ onscreen_native->crtc,
+ META_KMS_PAGE_FLIP_LISTENER_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;
+ }
+
+@@ -1160,9 +1258,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ "Postponing primary plane composite update for CRTC %u (%s)",
+ meta_kms_crtc_get_id (kms_crtc),
+ meta_kms_device_get_path (kms_device));
+-
+- clutter_frame_set_result (frame,
+- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+ return;
+ }
+ else if (meta_renderer_native_has_pending_mode_set (renderer_native))
+@@ -1172,8 +1267,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+
+ meta_renderer_native_notify_mode_sets_reset (renderer_native);
+ meta_renderer_native_post_mode_set_updates (renderer_native);
+- clutter_frame_set_result (frame,
+- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+ return;
+ }
+ break;
+@@ -1186,8 +1279,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ {
+ meta_renderer_native_notify_mode_sets_reset (renderer_native);
+ meta_renderer_native_post_mode_set_updates (renderer_native);
+- clutter_frame_set_result (frame,
+- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+ return;
+ }
+ break;
+@@ -1200,18 +1291,16 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
+ meta_kms_device_get_path (kms_device));
+
+ flags = META_KMS_UPDATE_FLAG_NONE;
+- kms_feedback = meta_kms_post_pending_update_sync (kms, kms_device, flags);
++ kms_feedback = meta_kms_post_pending_update_for_crtc_sync (kms,
++ kms_crtc,
++ flags);
++ g_return_if_fail (kms_feedback != NULL);
+
+ switch (meta_kms_feedback_get_result (kms_feedback))
+ {
+ case META_KMS_FEEDBACK_PASSED:
+- clutter_frame_set_result (frame,
+- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+ break;
+ case META_KMS_FEEDBACK_FAILED:
+- clutter_frame_set_result (frame,
+- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
+-
+ feedback_error = meta_kms_feedback_get_error (kms_feedback);
+ if (!g_error_matches (feedback_error,
+ G_IO_ERROR,
+@@ -1298,6 +1387,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
+ return FALSE;
+ }
+
++ /* Our direct scanout frame counts as 1, so more than that means we would
++ * be jumping the queue (and post would fail).
++ */
++ if (cogl_onscreen_count_pending_frames (onscreen) > 1)
++ {
++ g_set_error_literal (error,
++ COGL_SCANOUT_ERROR,
++ COGL_SCANOUT_ERROR_INHIBITED,
++ "Direct scanout is inhibited during triple buffering");
++ return FALSE;
++ }
++
+ renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native,
+ render_gpu);
+
+@@ -1350,7 +1451,9 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
+ meta_kms_device_get_path (kms_device));
+
+ flags = META_KMS_UPDATE_FLAG_PRESERVE_ON_ERROR;
+- kms_feedback = meta_kms_post_pending_update_sync (kms, kms_device, flags);
++ kms_feedback = meta_kms_post_pending_update_for_crtc_sync (kms,
++ kms_crtc,
++ flags);
+ switch (meta_kms_feedback_get_result (kms_feedback))
+ {
+ case META_KMS_FEEDBACK_PASSED:
+@@ -1364,7 +1467,6 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
+ G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED))
+ break;
+
+- g_clear_object (&onscreen_native->gbm.next_fb);
+ g_propagate_error (error, g_error_copy (feedback_error));
+ return FALSE;
+ }
+@@ -1400,7 +1502,10 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
+ const GError *error;
+
+- kms_update = meta_kms_get_pending_update (kms, kms_device);
++ if (cogl_onscreen_count_pending_frames (onscreen) > 0)
++ return;
++
++ kms_update = meta_kms_get_pending_update_for_crtc (kms, kms_crtc);
+ if (!kms_update)
+ {
+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
+@@ -1415,9 +1520,9 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
+ g_object_unref);
+
+ flags = META_KMS_UPDATE_FLAG_NONE;
+- kms_feedback = meta_kms_post_pending_update_sync (kms,
+- kms_device,
+- flags);
++ kms_feedback = meta_kms_post_pending_update_for_crtc_sync (kms,
++ kms_crtc,
++ flags);
+ switch (meta_kms_feedback_get_result (kms_feedback))
+ {
+ case META_KMS_FEEDBACK_PASSED:
+@@ -1439,6 +1544,17 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
+ }
+ }
+
++void
++meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen)
++{
++ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
++
++ onscreen_native->swaps_pending = 0;
++
++ g_clear_object (&onscreen_native->gbm.stalled_fb);
++ g_clear_object (&onscreen_native->gbm.next_fb);
++}
++
+ static gboolean
+ should_surface_be_sharable (CoglOnscreen *onscreen)
+ {
+@@ -1987,6 +2103,21 @@ pick_secondary_gpu_framebuffer_format_for_cpu (CoglOnscreen *onscreen)
+ return DRM_FORMAT_INVALID;
+ }
+
++static void
++dumb_toggle_notify (gpointer data,
++ GObject *object,
++ gboolean is_last_ref)
++{
++ MetaDrmBuffer **source_fb = data;
++
++ g_return_if_fail (source_fb != NULL);
++ if (is_last_ref && *source_fb)
++ {
++ g_return_if_fail (META_IS_DRM_BUFFER (*source_fb));
++ g_clear_object (source_fb);
++ }
++}
++
+ static gboolean
+ init_secondary_gpu_state_cpu_copy_mode (MetaRendererNative *renderer_native,
+ CoglOnscreen *onscreen,
+@@ -2043,6 +2174,12 @@ init_secondary_gpu_state_cpu_copy_mode (MetaRendererNative *renderer_nat
+ }
+
+ secondary_gpu_state->cpu.dumb_fbs[i] = META_DRM_BUFFER_DUMB (dumb_buffer);
++ g_object_add_toggle_ref (G_OBJECT (dumb_buffer),
++ dumb_toggle_notify,
++ &secondary_gpu_state->cpu.source_fbs[i]);
++
++ /* It was incremented higher than we need by add_toggle_ref */
++ g_object_unref (dumb_buffer);
+ }
+
+ /*
+@@ -2132,7 +2269,7 @@ meta_onscreen_native_new (MetaRendererNative *renderer_native,
+ onscreen_native->renderer_native = renderer_native;
+ onscreen_native->render_gpu = render_gpu;
+ onscreen_native->output = output;
+- onscreen_native->crtc = crtc;
++ onscreen_native->crtc = g_object_ref (crtc);
+
+ return onscreen_native;
+ }
+@@ -2153,7 +2290,6 @@ meta_onscreen_native_dispose (GObject *object)
+ {
+ case META_RENDERER_NATIVE_MODE_GBM:
+ g_clear_object (&onscreen_native->gbm.next_fb);
+- free_current_bo (onscreen);
+ break;
+ case META_RENDERER_NATIVE_MODE_SURFACELESS:
+ g_assert_not_reached ();
+@@ -2181,9 +2317,12 @@ meta_onscreen_native_dispose (GObject *object)
+
+ G_OBJECT_CLASS (meta_onscreen_native_parent_class)->dispose (object);
+
++ g_clear_object (&onscreen_native->crtc);
+ g_clear_pointer (&onscreen_native->gbm.surface, gbm_surface_destroy);
+ g_clear_pointer (&onscreen_native->secondary_gpu_state,
+ secondary_gpu_state_free);
++ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free);
++ onscreen_native->next_post.n_rectangles = 0;
+ }
+
+ static void
+diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h
+index 8ea7ec2546fff96260c6696ea0efd643a21e7914..81b0eeaed5c6e79d2c0b14f9190a1490cf63b905 100644
+--- a/src/backends/native/meta-onscreen-native.h
++++ b/src/backends/native/meta-onscreen-native.h
+@@ -42,6 +42,8 @@ void meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
+
+ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen);
+
++void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen);
++
+ gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen,
+ MetaDrmBuffer *fb);
+
+diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
+index 99fbb2bb65f68f5ea0a8148b4c6ef3511775a3af..d86a5f4b4ec0b41915ec02ed3394fdb8038cc4d8 100644
+--- a/src/backends/native/meta-renderer-native.c
++++ b/src/backends/native/meta-renderer-native.c
+@@ -677,12 +677,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;
+@@ -694,6 +700,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 =
+@@ -1402,6 +1411,26 @@ meta_renderer_native_create_view (MetaRenderer *renderer,
+ return view;
+ }
+
++static void
++discard_pending_swaps (MetaRenderer *renderer)
++{
++ GList *views = meta_renderer_get_views (renderer);;
++ GList *l;
++
++ for (l = views; l; l = l->next)
++ {
++ ClutterStageView *stage_view = l->data;
++ CoglFramebuffer *fb = clutter_stage_view_get_onscreen (stage_view);
++ CoglOnscreen *onscreen;
++
++ if (!COGL_IS_ONSCREEN (fb))
++ continue;
++
++ onscreen = COGL_ONSCREEN (fb);
++ meta_onscreen_native_discard_pending_swaps (onscreen);
++ }
++}
++
+ static void
+ keep_current_onscreens_alive (MetaRenderer *renderer)
+ {
+@@ -1430,6 +1459,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer)
+ MetaRendererClass *parent_renderer_class =
+ META_RENDERER_CLASS (meta_renderer_native_parent_class);
+
++ discard_pending_swaps (renderer);
+ meta_kms_discard_pending_page_flips (kms);
+ meta_kms_discard_pending_updates (kms);
+
+diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c
+index 6595eb5a290e02f91c8968085f424bbd3afef816..42f7e62760416ded2c7c7bcb146f617fb7f2a714 100644
+--- a/src/tests/native-kms-render.c
++++ b/src/tests/native-kms-render.c
+@@ -36,6 +36,8 @@
+ #include "tests/meta-wayland-test-driver.h"
+ #include "tests/meta-wayland-test-utils.h"
+
++#define N_FRAMES_PER_TEST 10
++
+ typedef struct
+ {
+ int number_of_frames_left;
+@@ -43,7 +45,9 @@ typedef struct
+
+ struct {
+ int n_paints;
+- uint32_t fb_id;
++ int n_presentations;
++ int n_direct_scanouts;
++ GList *fb_ids;
+ } scanout;
+
+ gboolean wait_for_scanout;
+@@ -72,7 +76,7 @@ meta_test_kms_render_basic (void)
+ gulong handler_id;
+
+ test = (KmsRenderingTest) {
+- .number_of_frames_left = 10,
++ .number_of_frames_left = N_FRAMES_PER_TEST,
+ .loop = g_main_loop_new (NULL, FALSE),
+ };
+ handler_id = g_signal_connect (stage, "after-update",
+@@ -93,7 +97,6 @@ on_scanout_before_update (ClutterStage *stage,
+ KmsRenderingTest *test)
+ {
+ test->scanout.n_paints = 0;
+- test->scanout.fb_id = 0;
+ }
+
+ static void
+@@ -103,6 +106,7 @@ on_scanout_before_paint (ClutterStage *stage,
+ {
+ CoglScanout *scanout;
+ MetaDrmBuffer *buffer;
++ uint32_t fb_id;
+
+ scanout = clutter_stage_view_peek_scanout (stage_view);
+ if (!scanout)
+@@ -110,8 +114,14 @@ on_scanout_before_paint (ClutterStage *stage,
+
+ g_assert_true (META_IS_DRM_BUFFER (scanout));
+ buffer = META_DRM_BUFFER (scanout);
+- test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer);
+- g_assert_cmpuint (test->scanout.fb_id, >, 0);
++
++ fb_id = meta_drm_buffer_get_fb_id (buffer);
++ g_assert_cmpuint (fb_id, >, 0);
++ test->scanout.fb_ids = g_list_append (test->scanout.fb_ids,
++ GUINT_TO_POINTER (fb_id));
++
++ /* Triple buffering, but no higher */
++ g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2);
+ }
+
+ static void
+@@ -138,12 +148,12 @@ on_scanout_presented (ClutterStage *stage,
+ MetaDeviceFile *device_file;
+ GError *error = NULL;
+ drmModeCrtc *drm_crtc;
++ uint32_t first_fb_id_expected;
+
+- if (test->scanout.n_paints > 0)
++ if (test->wait_for_scanout && test->scanout.fb_ids == NULL)
+ return;
+
+- if (test->wait_for_scanout && test->scanout.fb_id == 0)
+- return;
++ test->scanout.n_presentations++;
+
+ device_pool = meta_backend_native_get_device_pool (backend_native);
+
+@@ -162,15 +172,41 @@ on_scanout_presented (ClutterStage *stage,
+ drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file),
+ meta_kms_crtc_get_id (kms_crtc));
+ g_assert_nonnull (drm_crtc);
+- if (test->scanout.fb_id == 0)
+- g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id);
++
++ if (test->scanout.fb_ids)
++ {
++ test->scanout.n_direct_scanouts++;
++ first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data);
++ test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids,
++ test->scanout.fb_ids);
++ }
+ else
+- g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id);
++ {
++ first_fb_id_expected = 0;
++ }
++
++ /* The buffer ID won't match on the first frame because switching from
++ * triple buffered compositing to double buffered direct scanout takes
++ * an extra frame to drain the queue. Thereafter we are in direct scanout
++ * mode and expect the buffer IDs to match.
++ */
++ if (test->scanout.n_presentations > 1)
++ {
++ if (first_fb_id_expected == 0)
++ g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected);
++ else
++ g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected);
++ }
++
+ drmModeFreeCrtc (drm_crtc);
+
+ meta_device_file_release (device_file);
+
+- g_main_loop_quit (test->loop);
++ test->number_of_frames_left--;
++ if (test->number_of_frames_left <= 0)
++ g_main_loop_quit (test->loop);
++ else
++ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ }
+
+ typedef enum
+@@ -209,7 +245,9 @@ meta_test_kms_render_client_scanout (void)
+ g_assert_nonnull (wayland_test_client);
+
+ test = (KmsRenderingTest) {
++ .number_of_frames_left = N_FRAMES_PER_TEST,
+ .loop = g_main_loop_new (NULL, FALSE),
++ .scanout = {0},
+ .wait_for_scanout = TRUE,
+ };
+
+@@ -235,7 +273,8 @@ meta_test_kms_render_client_scanout (void)
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ g_main_loop_run (test.loop);
+
+- g_assert_cmpuint (test.scanout.fb_id, >, 0);
++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST);
+
+ g_debug ("Unmake fullscreen");
+ window = meta_find_window_from_title (test_context, "dma-buf-scanout-test");
+@@ -257,10 +296,15 @@ meta_test_kms_render_client_scanout (void)
+ g_assert_cmpint (buffer_rect.y, ==, 10);
+
+ test.wait_for_scanout = FALSE;
++ test.number_of_frames_left = N_FRAMES_PER_TEST;
++ test.scanout.n_presentations = 0;
++ test.scanout.n_direct_scanouts = 0;
++
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ g_main_loop_run (test.loop);
+
+- g_assert_cmpuint (test.scanout.fb_id, ==, 0);
++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 0);
+
+ g_debug ("Moving back to 0, 0");
+ meta_window_move_frame (window, TRUE, 0, 0);
+@@ -272,10 +316,15 @@ meta_test_kms_render_client_scanout (void)
+ g_assert_cmpint (buffer_rect.y, ==, 0);
+
+ test.wait_for_scanout = TRUE;
++ test.number_of_frames_left = N_FRAMES_PER_TEST;
++ test.scanout.n_presentations = 0;
++ test.scanout.n_direct_scanouts = 0;
++
+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
+ g_main_loop_run (test.loop);
+
+- g_assert_cmpuint (test.scanout.fb_id, >, 0);
++ g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST);
++ g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST);
+
+ g_signal_handler_disconnect (stage, before_update_handler_id);
+ g_signal_handler_disconnect (stage, before_paint_handler_id);