summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorohno10522024-02-16 14:33:44 +0500
committerohno10522024-02-16 14:33:44 +0500
commitc5da8df6d1241b5bcb29d9cbb6d0253ce41e699f (patch)
treec885027b9f0eda5d1d0c43adda893e355cd602b4
parent5ea4c58e8a9fb3d2d356c9f4609ce19415b69dc6 (diff)
downloadaur-mutter-auto-rotation.tar.gz
45.4
-rw-r--r--.SRCINFO6
-rw-r--r--PKGBUILD7
-rw-r--r--mr1441.patch2312
3 files changed, 4 insertions, 2321 deletions
diff --git a/.SRCINFO b/.SRCINFO
index f676d7549cb1..93b263bb75ee 100644
--- a/.SRCINFO
+++ b/.SRCINFO
@@ -1,6 +1,6 @@
pkgbase = mutter-auto-rotation
pkgdesc = Window manager and compositor for GNOME, with touch-mode auto-rotation (reverts MR 1710)
- pkgver = 45.3
+ pkgver = 45.4
pkgrel = 1
url = https://gitlab.gnome.org/GNOME/mutter
arch = x86_64
@@ -43,11 +43,9 @@ pkgbase = mutter-auto-rotation
options = strip
options = !docs
options = lto
- source = git+https://gitlab.gnome.org/GNOME/mutter.git#tag=45.3
- source = mr1441.patch
+ source = git+https://gitlab.gnome.org/GNOME/mutter.git#tag=45.4
source = 0001-Revert-backends-native-Disable-touch-mode-with-point.patch
sha256sums = SKIP
- sha256sums = 5b0e927eb2873256c7999ebc711f5f0db2296550d7dbe51e757335e2b77d016c
sha256sums = b11e3e1c9a14b9d9a3941e4495f7f16cfe84c53c6b8e571df049546840a91a46
pkgname = mutter-auto-rotation
diff --git a/PKGBUILD b/PKGBUILD
index da47b76329fe..11476132d98c 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -8,7 +8,7 @@ pkgbase=mutter-auto-rotation
pkgname=mutter-auto-rotation
provides=(libmutter-13.so mutter)
conflicts=(mutter)
-pkgver=45.3
+pkgver=45.4
pkgrel=1
pkgdesc="Window manager and compositor for GNOME, with touch-mode auto-rotation (reverts MR 1710)"
url="https://gitlab.gnome.org/GNOME/mutter"
@@ -55,13 +55,11 @@ makedepends=(
source=(
"git+https://gitlab.gnome.org/GNOME/mutter.git#tag=$_tag"
- "mr1441.patch"
"0001-Revert-backends-native-Disable-touch-mode-with-point.patch"
)
sha256sums=(
'SKIP'
- '5b0e927eb2873256c7999ebc711f5f0db2296550d7dbe51e757335e2b77d016c'
'b11e3e1c9a14b9d9a3941e4495f7f16cfe84c53c6b8e571df049546840a91a46'
)
@@ -72,7 +70,7 @@ pkgver() {
prepare() {
cd mutter
- git apply ../mr1441.patch
+
git apply ../0001-Revert-backends-native-Disable-touch-mode-with-point.patch
}
@@ -84,7 +82,6 @@ build() {
-D installed_tests=false
-D libdisplay_info=true
-D wayland_eglstream=true
- -D tests=false
--buildtype=release
)
diff --git a/mr1441.patch b/mr1441.patch
deleted file mode 100644
index 84f8d47715a3..000000000000
--- a/mr1441.patch
+++ /dev/null
@@ -1,2312 +0,0 @@
-Author: Daniel van Vugt <daniel.van.vugt@canonical.com>
-Source: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441
-Commit: 0b896518b2028d9c4d6ea44806d093fd33793689
-Rebase: Fri Dec 8 16:31:18 2023 +0800
-
-diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c
-index ab493e0b0..905d8c926 100644
---- a/clutter/clutter/clutter-frame-clock.c
-+++ b/clutter/clutter/clutter-frame-clock.c
-@@ -35,6 +35,15 @@ enum
-
- static guint signals[N_SIGNALS];
-
-+typedef enum
-+{
-+ TRIPLE_BUFFERING_MODE_NEVER,
-+ TRIPLE_BUFFERING_MODE_AUTO,
-+ TRIPLE_BUFFERING_MODE_ALWAYS,
-+} TripleBufferingMode;
-+
-+static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO;
-+
- #define SYNC_DELAY_FALLBACK_FRACTION 0.875
-
- typedef struct _ClutterFrameListener
-@@ -55,8 +64,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
-@@ -73,6 +83,7 @@ struct _ClutterFrameClock
-
- ClutterFrameClockState state;
- int64_t last_dispatch_time_us;
-+ int64_t prev_last_dispatch_time_us;
- int64_t last_dispatch_lateness_us;
- int64_t last_presentation_time_us;
- int64_t next_update_time_us;
-@@ -87,6 +98,9 @@ struct _ClutterFrameClock
- int64_t vblank_duration_us;
- /* Last KMS buffer submission time. */
- int64_t last_flip_time_us;
-+ int64_t prev_last_flip_time_us;
-+
-+ ClutterFrameHint last_flip_hints;
-
- /* Last time we promoted short-term maximum to long-term one */
- int64_t longterm_promotion_us;
-@@ -219,10 +233,6 @@ static void
- maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock,
- ClutterFrameInfo *frame_info)
- {
-- /* Do not update long-term max if there has been no measurement */
-- if (!frame_clock->shortterm_max_update_duration_us)
-- return;
--
- if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) <
- G_USEC_PER_SEC)
- return;
-@@ -249,6 +259,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)");
-
-@@ -328,31 +344,58 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
-
- frame_clock->got_measurements_last_frame = FALSE;
-
-- if (frame_info->cpu_time_before_buffer_swap_us != 0)
-+ if (frame_info->cpu_time_before_buffer_swap_us != 0 ||
-+ frame_clock->ever_got_measurements)
- {
- int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us;
-+ int64_t dispatch_time_us = 0, flip_time_us = 0;
-
-- dispatch_to_swap_us =
-- frame_info->cpu_time_before_buffer_swap_us -
-- frame_clock->last_dispatch_time_us;
-+ switch (frame_clock->state)
-+ {
-+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
-+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
-+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
-+ g_warn_if_reached ();
-+ G_GNUC_FALLTHROUGH;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
-+ dispatch_time_us = frame_clock->last_dispatch_time_us;
-+ flip_time_us = frame_clock->last_flip_time_us;
-+ break;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
-+ dispatch_time_us = frame_clock->prev_last_dispatch_time_us;
-+ flip_time_us = frame_clock->prev_last_flip_time_us;
-+ break;
-+ }
-+
-+ if (frame_info->cpu_time_before_buffer_swap_us == 0)
-+ {
-+ /* Cursor-only updates with no "swap" or "flip" */
-+ dispatch_to_swap_us = 0;
-+ swap_to_flip_us = 0;
-+ }
-+ else
-+ {
-+ dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us -
-+ dispatch_time_us;
-+ swap_to_flip_us = flip_time_us -
-+ frame_info->cpu_time_before_buffer_swap_us;
-+ }
- swap_to_rendering_done_us =
- frame_info->gpu_rendering_duration_ns / 1000;
-- swap_to_flip_us =
-- frame_clock->last_flip_time_us -
-- frame_info->cpu_time_before_buffer_swap_us;
-
- CLUTTER_NOTE (FRAME_TIMINGS,
-- "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
-+ "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs",
-+ debug_state,
- frame_clock->last_dispatch_lateness_us,
- dispatch_to_swap_us,
- swap_to_rendering_done_us,
- swap_to_flip_us);
-
- frame_clock->shortterm_max_update_duration_us =
-- CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us +
-- MAX (swap_to_rendering_done_us, swap_to_flip_us),
-- frame_clock->shortterm_max_update_duration_us,
-- frame_clock->refresh_interval_us);
-+ MAX (frame_clock->shortterm_max_update_duration_us,
-+ frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us +
-+ MAX (swap_to_rendering_done_us, swap_to_flip_us));
-
- maybe_update_longterm_max_duration_us (frame_clock, frame_info);
-
-@@ -361,7 +404,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
- }
- else
- {
-- CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs",
-+ CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs",
-+ debug_state,
- frame_clock->last_dispatch_lateness_us);
- }
-
-@@ -378,11 +422,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;
- }
- }
-
-@@ -398,11 +449,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;
- }
- }
-
-@@ -417,7 +475,14 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock)
- if (!frame_clock->ever_got_measurements ||
- G_UNLIKELY (clutter_paint_debug_flags &
- CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME))
-- return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION;
-+ {
-+ int64_t ret = refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION;
-+
-+ if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE)
-+ ret += refresh_interval_us;
-+
-+ return ret;
-+ }
-
- /* Max render time shows how early the frame clock needs to be dispatched
- * to make it to the predicted next presentation time. It is an estimate of
-@@ -437,8 +502,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;
- }
-
-@@ -453,8 +516,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
- int64_t refresh_interval_us;
- int64_t min_render_time_allowed_us;
- int64_t max_render_time_allowed_us;
-- int64_t next_presentation_time_us;
-+ int64_t next_presentation_time_us = 0;
- int64_t next_update_time_us;
-+ gboolean skipped_frames = FALSE;
-
- now_us = g_get_monotonic_time ();
-
-@@ -498,7 +562,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
- *
- */
- last_presentation_time_us = frame_clock->last_presentation_time_us;
-- next_presentation_time_us = last_presentation_time_us + refresh_interval_us;
-+ switch (frame_clock->state)
-+ {
-+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
-+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
-+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
-+ next_presentation_time_us = last_presentation_time_us +
-+ refresh_interval_us;
-+ break;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
-+ next_presentation_time_us = last_presentation_time_us +
-+ 2 * refresh_interval_us;
-+ break;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
-+ next_presentation_time_us = last_presentation_time_us +
-+ 3 * refresh_interval_us;
-+ break;
-+ }
-
- /*
- * However, the last presentation could have happened more than a frame ago.
-@@ -534,6 +615,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
-
- current_phase_us = (now_us - last_presentation_time_us) % refresh_interval_us;
- next_presentation_time_us = now_us - current_phase_us + refresh_interval_us;
-+ skipped_frames = TRUE;
- }
-
- if (frame_clock->is_next_presentation_time_valid)
-@@ -566,7 +648,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
- }
- }
-
-- if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us)
-+ if (skipped_frames)
- {
- /* There was an idle period since the last presentation, so there seems
- * be no constantly updating actor. In this case it's best to start
-@@ -607,8 +689,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;
- }
-
-@@ -645,9 +731,15 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
- case CLUTTER_FRAME_CLOCK_STATE_IDLE:
- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
- next_update_time_us = g_get_monotonic_time ();
-+ frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
-+ break;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED:
-+ next_update_time_us = g_get_monotonic_time ();
-+ frame_clock->state =
-+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED;
- break;
-- case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
-- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
- frame_clock->pending_reschedule = TRUE;
- frame_clock->pending_reschedule_now = TRUE;
- return;
-@@ -657,7 +749,6 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
-
- frame_clock->next_update_time_us = next_update_time_us;
- g_source_set_ready_time (frame_clock->source, next_update_time_us);
-- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
- frame_clock->is_next_presentation_time_valid = FALSE;
- }
-
-@@ -665,6 +756,12 @@ void
- clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
- {
- int64_t next_update_time_us = -1;
-+ TripleBufferingMode current_mode = triple_buffering_mode;
-+
-+ if (current_mode == TRIPLE_BUFFERING_MODE_AUTO &&
-+ (frame_clock->last_flip_hints &
-+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED))
-+ current_mode = TRIPLE_BUFFERING_MODE_NEVER;
-
- if (frame_clock->inhibit_count > 0)
- {
-@@ -676,6 +773,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,
-@@ -684,11 +782,37 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
- &frame_clock->min_render_time_allowed_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:
-+ switch (current_mode)
-+ {
-+ case TRIPLE_BUFFERING_MODE_NEVER:
-+ frame_clock->pending_reschedule = TRUE;
-+ return;
-+ case TRIPLE_BUFFERING_MODE_AUTO:
-+ calculate_next_update_time_us (frame_clock,
-+ &next_update_time_us,
-+ &frame_clock->next_presentation_time_us,
-+ &frame_clock->min_render_time_allowed_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 TRIPLE_BUFFERING_MODE_ALWAYS:
-+ next_update_time_us = g_get_monotonic_time ();
-+ frame_clock->next_presentation_time_us = 0;
-+ frame_clock->is_next_presentation_time_valid = FALSE;
-+ frame_clock->state =
-+ CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED;
-+ break;
-+ }
-+ break;
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
- frame_clock->pending_reschedule = TRUE;
- return;
- }
-@@ -697,7 +821,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
-
- frame_clock->next_update_time_us = next_update_time_us;
- g_source_set_ready_time (frame_clock->source, next_update_time_us);
-- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
- }
-
- static void
-@@ -728,7 +851,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;
-@@ -749,10 +872,25 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
- }
- #endif
-
-+ frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us;
- frame_clock->last_dispatch_time_us = time_us;
- g_source_set_ready_time (frame_clock->source, -1);
-
-- frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING;
-+ switch (frame_clock->state)
-+ {
-+ case CLUTTER_FRAME_CLOCK_STATE_INIT:
-+ case CLUTTER_FRAME_CLOCK_STATE_IDLE:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE:
-+ case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO:
-+ g_warn_if_reached ();
-+ return;
-+ case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
-+ 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++;
-
-@@ -781,25 +919,31 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
- result = iface->frame (frame_clock, frame, frame_clock->listener.user_data);
- COGL_TRACE_END (ClutterFrameClockFrame);
-
-- switch (frame_clock->state)
-+ switch (result)
- {
-- case CLUTTER_FRAME_CLOCK_STATE_INIT:
-- case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
-- g_warn_if_reached ();
-- break;
-- case CLUTTER_FRAME_CLOCK_STATE_IDLE:
-- case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
-+ case CLUTTER_FRAME_RESULT_PENDING_PRESENTED:
- 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;
- }
-@@ -832,10 +976,13 @@ frame_clock_source_dispatch (GSource *source,
- }
-
- void
--clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock,
-- int64_t flip_time_us)
-+clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
-+ int64_t flip_time_us,
-+ ClutterFrameHint hints)
- {
-+ frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us;
- frame_clock->last_flip_time_us = flip_time_us;
-+ frame_clock->last_flip_hints = hints;
- }
-
- GString *
-@@ -929,8 +1076,6 @@ clutter_frame_clock_dispose (GObject *object)
- {
- ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object);
-
-- g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING);
--
- if (frame_clock->source)
- {
- g_signal_emit (frame_clock, signals[DESTROY], 0);
-@@ -951,6 +1096,15 @@ static void
- clutter_frame_clock_class_init (ClutterFrameClockClass *klass)
- {
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
-+ const char *mode_str;
-+
-+ mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING");
-+ if (!g_strcmp0 (mode_str, "never"))
-+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER;
-+ else if (!g_strcmp0 (mode_str, "auto"))
-+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO;
-+ else if (!g_strcmp0 (mode_str, "always"))
-+ triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS;
-
- object_class->dispose = clutter_frame_clock_dispose;
-
-diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h
-index 93ebc9438..e1fd6b986 100644
---- a/clutter/clutter/clutter-frame-clock.h
-+++ b/clutter/clutter/clutter-frame-clock.h
-@@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult
- CLUTTER_FRAME_RESULT_IDLE,
- } ClutterFrameResult;
-
-+typedef enum _ClutterFrameHint
-+{
-+ CLUTTER_FRAME_HINT_NONE = 0,
-+ CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0,
-+} ClutterFrameHint;
-+
- #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ())
- CLUTTER_EXPORT
- G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock,
-@@ -91,7 +97,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock,
- CLUTTER_EXPORT
- float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock);
-
--void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock,
-- int64_t flip_time_us);
-+void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock,
-+ int64_t flip_time_us,
-+ ClutterFrameHint hints);
-
- GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock);
-diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h
-index 0a0226b0a..55c76df72 100644
---- a/clutter/clutter/clutter-frame-private.h
-+++ b/clutter/clutter/clutter-frame-private.h
-@@ -34,6 +34,7 @@ struct _ClutterFrame
-
- gboolean has_result;
- ClutterFrameResult result;
-+ ClutterFrameHint hints;
- };
-
- CLUTTER_EXPORT
-diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c
-index 85baef274..413ce9c2b 100644
---- a/clutter/clutter/clutter-frame.c
-+++ b/clutter/clutter/clutter-frame.c
-@@ -113,3 +113,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 1d5660d68..0e7f618a4 100644
---- a/clutter/clutter/clutter-frame.h
-+++ b/clutter/clutter/clutter-frame.h
-@@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame,
- CLUTTER_EXPORT
- gboolean clutter_frame_has_result (ClutterFrame *frame);
-
-+CLUTTER_EXPORT
-+void clutter_frame_set_hint (ClutterFrame *frame,
-+ ClutterFrameHint hint);
-+
-+CLUTTER_EXPORT
-+ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame);
-+
- G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref)
-diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c
-index 168746dd4..f0e36619e 100644
---- a/clutter/clutter/clutter-stage-view.c
-+++ b/clutter/clutter/clutter-stage-view.c
-@@ -1264,14 +1264,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock,
-
- _clutter_stage_window_redraw_view (stage_window, view, frame);
-
-- clutter_frame_clock_record_flip_time (frame_clock,
-- g_get_monotonic_time ());
-+ clutter_frame_clock_record_flip (frame_clock,
-+ g_get_monotonic_time (),
-+ clutter_frame_get_hints (frame));
-
- clutter_stage_emit_after_paint (stage, view, frame);
-
- if (_clutter_context_get_show_fps ())
- end_frame_timing_measurement (view);
- }
-+ else
-+ {
-+ clutter_frame_clock_record_flip (frame_clock,
-+ g_get_monotonic_time (),
-+ clutter_frame_get_hints (frame));
-+ }
-
- _clutter_stage_window_finish_frame (stage_window, view, frame);
-
-diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h
-index 9dbecfd0c..681d91d2b 100644
---- a/cogl/cogl/cogl-onscreen-private.h
-+++ b/cogl/cogl/cogl-onscreen-private.h
-@@ -95,3 +95,6 @@ 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);
-diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c
-index 73425e498..02c4474b2 100644
---- a/cogl/cogl/cogl-onscreen.c
-+++ b/cogl/cogl/cogl-onscreen.c
-@@ -508,6 +508,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 c35cb36e3..2130e4042 100644
---- a/src/backends/meta-stage-impl.c
-+++ b/src/backends/meta-stage-impl.c
-@@ -775,6 +775,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window,
- {
- g_autoptr (GError) error = NULL;
-
-+ clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED);
-+
- if (meta_stage_impl_scanout_view (stage_impl,
- stage_view,
- scanout,
-diff --git a/src/backends/native/meta-kms-crtc.c b/src/backends/native/meta-kms-crtc.c
-index d89b12598..b17e8460d 100644
---- a/src/backends/native/meta-kms-crtc.c
-+++ b/src/backends/native/meta-kms-crtc.c
-@@ -48,6 +48,8 @@ struct _MetaKmsCrtc
- MetaKmsCrtcState current_state;
-
- MetaKmsCrtcPropTable prop_table;
-+
-+ MetaSwapChain *swap_chain;
- };
-
- G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT)
-@@ -99,6 +101,12 @@ meta_kms_crtc_get_prop_drm_value (MetaKmsCrtc *crtc,
- return meta_kms_prop_convert_value (prop, value);
- }
-
-+MetaSwapChain *
-+meta_kms_crtc_get_swap_chain (MetaKmsCrtc *crtc)
-+{
-+ return crtc->swap_chain;
-+}
-+
- gboolean
- meta_kms_crtc_is_active (MetaKmsCrtc *crtc)
- {
-@@ -465,12 +473,23 @@ meta_kms_crtc_new (MetaKmsImplDevice *impl_device,
- return crtc;
- }
-
-+static void
-+meta_kms_crtc_dispose (GObject *object)
-+{
-+ MetaKmsCrtc *crtc = META_KMS_CRTC (object);
-+
-+ meta_swap_chain_release_buffers (crtc->swap_chain);
-+
-+ G_OBJECT_CLASS (meta_kms_crtc_parent_class)->dispose (object);
-+}
-+
- static void
- meta_kms_crtc_finalize (GObject *object)
- {
- MetaKmsCrtc *crtc = META_KMS_CRTC (object);
-
- g_clear_pointer (&crtc->current_state.gamma.value, meta_gamma_lut_free);
-+ g_clear_object (&crtc->swap_chain);
-
- G_OBJECT_CLASS (meta_kms_crtc_parent_class)->finalize (object);
- }
-@@ -480,6 +499,7 @@ meta_kms_crtc_init (MetaKmsCrtc *crtc)
- {
- crtc->current_state.gamma.size = 0;
- crtc->current_state.gamma.value = NULL;
-+ crtc->swap_chain = meta_swap_chain_new ();
- }
-
- static void
-@@ -487,6 +507,7 @@ 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 b26b682dd..a30a6de6e 100644
---- a/src/backends/native/meta-kms-crtc.h
-+++ b/src/backends/native/meta-kms-crtc.h
-@@ -22,6 +22,7 @@
- #include <xf86drmMode.h>
-
- #include "backends/native/meta-kms-types.h"
-+#include "backends/native/meta-swap-chain.h"
- #include "backends/meta-backend-types.h"
- #include "core/util-private.h"
- #include "meta/boxes.h"
-@@ -60,3 +61,5 @@ int meta_kms_crtc_get_idx (MetaKmsCrtc *crtc);
-
- META_EXPORT_TEST
- gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc);
-+
-+MetaSwapChain * meta_kms_crtc_get_swap_chain (MetaKmsCrtc *crtc);
-diff --git a/src/backends/native/meta-kms-impl-device-atomic.c b/src/backends/native/meta-kms-impl-device-atomic.c
-index 2ca70326f..80c01413c 100644
---- a/src/backends/native/meta-kms-impl-device-atomic.c
-+++ b/src/backends/native/meta-kms-impl-device-atomic.c
-@@ -505,6 +505,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;
-@@ -657,6 +658,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
- error))
- return FALSE;
- }
-+
-+ if (!(flags & META_KMS_UPDATE_FLAG_TEST_ONLY))
-+ meta_swap_chain_push_buffer (meta_kms_crtc_get_swap_chain (plane_assignment->crtc),
-+ meta_kms_plane_get_id (plane),
-+ G_OBJECT (buffer));
-+
- return TRUE;
- }
-
-@@ -1005,7 +1012,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 2d68ba11f..f4e23df07 100644
---- a/src/backends/native/meta-kms-impl-device-simple.c
-+++ b/src/backends/native/meta-kms-impl-device-simple.c
-@@ -485,6 +485,8 @@ process_mode_set (MetaKmsImplDevice *impl_device,
- return FALSE;
- }
-
-+ meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (crtc));
-+
- if (drm_mode)
- {
- g_hash_table_replace (impl_device_simple->cached_mode_sets,
-@@ -554,7 +556,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;
-@@ -567,6 +569,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);
- }
-
-@@ -634,16 +637,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);
- }
-@@ -730,7 +738,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)
-@@ -745,7 +753,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,
-@@ -877,6 +885,8 @@ mode_set_fallback (MetaKmsImplDeviceSimple *impl_device_simple,
- return FALSE;
- }
-
-+ meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (crtc));
-+
- if (!impl_device_simple->mode_set_fallback_feedback_source)
- {
- MetaKmsImpl *impl = meta_kms_impl_device_get_impl (impl_device);
-@@ -1003,20 +1013,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));
-@@ -1299,7 +1309,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,
-@@ -1313,7 +1323,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,
-@@ -1326,6 +1336,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device,
- }
-
- g_assert_not_reached ();
-+
-+assigned:
-+ meta_swap_chain_push_buffer (meta_kms_crtc_get_swap_chain (plane_assignment->crtc),
-+ meta_kms_plane_get_id (plane),
-+ G_OBJECT (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 bce64d309..85441f47b 100644
---- a/src/backends/native/meta-kms-impl-device.c
-+++ b/src/backends/native/meta-kms-impl-device.c
-@@ -1483,9 +1483,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device,
- meta_kms_update_merge_from (crtc_frame->pending_update, update);
- meta_kms_update_free (update);
- update = g_steal_pointer (&crtc_frame->pending_update);
-- disarm_crtc_frame_deadline_timer (crtc_frame);
- }
-
-+ if (crtc_frame->deadline.armed)
-+ disarm_crtc_frame_deadline_timer (crtc_frame);
-+
- meta_kms_device_handle_flush (priv->device, latch_crtc);
-
- feedback = do_process (impl_device, latch_crtc, update, flags);
-@@ -1862,6 +1864,16 @@ meta_kms_impl_device_init_mode_setting (MetaKmsImplDevice *impl_device,
- return TRUE;
- }
-
-+static void
-+release_buffers (gpointer data,
-+ gpointer user_data)
-+{
-+ MetaKmsCrtc *crtc = data;
-+ MetaSwapChain *swap_chain = meta_kms_crtc_get_swap_chain (crtc);
-+
-+ meta_swap_chain_release_buffers (swap_chain);
-+}
-+
- void
- meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device)
- {
-@@ -1869,6 +1881,8 @@ meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device)
- meta_kms_impl_device_get_instance_private (impl_device);
- MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device);
-
-+ g_list_foreach (priv->crtcs, release_buffers, NULL);
-+
- if (klass->prepare_shutdown)
- klass->prepare_shutdown (impl_device);
-
-diff --git a/src/backends/native/meta-kms-update.c b/src/backends/native/meta-kms-update.c
-index 5189c5ab3..bb7349ecf 100644
---- a/src/backends/native/meta-kms-update.c
-+++ b/src/backends/native/meta-kms-update.c
-@@ -190,6 +190,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);
- }
-
-@@ -292,7 +293,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,
-diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c
-index ec009ec8c..c6708946e 100644
---- a/src/backends/native/meta-kms.c
-+++ b/src/backends/native/meta-kms.c
-@@ -155,6 +155,8 @@ struct _MetaKms
- int kernel_thread_inhibit_count;
-
- MetaKmsCursorManager *cursor_manager;
-+
-+ gboolean shutting_down;
- };
-
- G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD)
-@@ -433,6 +435,7 @@ static void
- on_prepare_shutdown (MetaBackend *backend,
- MetaKms *kms)
- {
-+ kms->shutting_down = TRUE;
- meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL);
- meta_thread_flush_callbacks (META_THREAD (kms));
-
-@@ -487,6 +490,12 @@ meta_kms_new (MetaBackend *backend,
- return 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 743401406..f6b19520b 100644
---- a/src/backends/native/meta-kms.h
-+++ b/src/backends/native/meta-kms.h
-@@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms,
- MetaKmsDeviceFlag flags,
- GError **error);
-
-+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 2388a44a2..14d727c55 100644
---- a/src/backends/native/meta-onscreen-native.c
-+++ b/src/backends/native/meta-onscreen-native.c
-@@ -72,7 +72,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState
-
- struct {
- MetaDrmBufferDumb *current_dumb_fb;
-- MetaDrmBufferDumb *dumb_fbs[2];
-+ MetaDrmBufferDumb *dumb_fbs[3];
- } cpu;
-
- gboolean noted_primary_gpu_copy_ok;
-@@ -93,8 +93,13 @@ struct _MetaOnscreenNative
-
- struct {
- struct gbm_surface *surface;
-- MetaDrmBuffer *current_fb;
- MetaDrmBuffer *next_fb;
-+ MetaDrmBuffer *stalled_fb;
-+
-+ /* Temporary workaround for the scanout-failed signal wanting the buffer
-+ * to live longer than it does, and then it doesn't use it anyway...
-+ */
-+ MetaDrmBuffer *direct_fb;
- } gbm;
-
- #ifdef HAVE_EGL_DEVICE
-@@ -116,6 +121,16 @@ struct _MetaOnscreenNative
- gulong privacy_screen_changed_handler_id;
- gulong color_space_changed_handler_id;
- gulong hdr_metadata_changed_handler_id;
-+
-+ gboolean needs_flush;
-+
-+ unsigned int swaps_pending;
-+
-+ struct {
-+ int *rectangles; /* 4 x n_rectangles */
-+ int n_rectangles;
-+ ClutterFrame *frame;
-+ } next_post;
- };
-
- G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
-@@ -123,40 +138,17 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native,
-
- static GQuark blit_source_quark = 0;
-
--static gboolean
--init_secondary_gpu_state (MetaRendererNative *renderer_native,
-- CoglOnscreen *onscreen,
-- GError **error);
--
- static void
--free_current_bo (CoglOnscreen *onscreen)
--{
-- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
--
-- g_clear_object (&onscreen_native->gbm.current_fb);
--}
--
--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);
--}
-+try_post_latest_swap (CoglOnscreen *onscreen);
-
- static void
--meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen)
--{
-- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
-+post_finish_frame (MetaOnscreenNative *onscreen_native,
-+ MetaKmsUpdate *kms_update);
-
-- g_clear_object (&onscreen_native->gbm.next_fb);
--}
-+static gboolean
-+init_secondary_gpu_state (MetaRendererNative *renderer_native,
-+ CoglOnscreen *onscreen,
-+ GError **error);
-
- static void
- maybe_update_frame_info (MetaCrtc *crtc,
-@@ -193,7 +185,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);
-@@ -228,7 +220,8 @@ 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);
-+ meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (kms_crtc));
-+ try_post_latest_swap (onscreen);
- }
-
- static void
-@@ -278,15 +271,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc,
- CoglFramebuffer *framebuffer =
- clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view));
- CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
-- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- CoglFrameInfo *frame_info;
-
- frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
-
-- g_warn_if_fail (!onscreen_native->gbm.next_fb);
--
- meta_onscreen_native_notify_frame_complete (onscreen);
-+ try_post_latest_swap (onscreen);
- }
-
- static void
-@@ -336,7 +327,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_clear_next_fb (onscreen);
-+ try_post_latest_swap (onscreen);
- }
-
- static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = {
-@@ -397,18 +388,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)
- {
- CoglFrameInfo *frame_info;
-+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
-
-- 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,
-@@ -425,7 +438,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
- MetaRendererNativeGpuData *renderer_gpu_data;
- MetaGpuKms *gpu_kms;
-- MetaDrmBuffer *buffer;
-+ g_autoptr (MetaDrmBuffer) buffer = NULL;
- MetaKmsPlaneAssignment *plane_assignment;
-
- COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs,
-@@ -440,7 +453,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen,
- switch (renderer_gpu_data->mode)
- {
- case META_RENDERER_NATIVE_MODE_GBM:
-- buffer = onscreen_native->gbm.next_fb;
-+ buffer = g_steal_pointer (&onscreen_native->gbm.next_fb);
-
- plane_assignment = meta_crtc_kms_assign_primary_plane (crtc_kms,
- buffer,
-@@ -596,6 +609,16 @@ import_shared_framebuffer (CoglOnscreen *onscreen,
- return imported_buffer;
- }
-
-+static void
-+reference_owning_gbm_surface (CoglOnscreen *onscreen,
-+ MetaDrmBufferGbm *buffer_gbm)
-+{
-+ g_object_set_data_full (G_OBJECT (buffer_gbm),
-+ "gbm_surface owner",
-+ g_object_ref (onscreen),
-+ (GDestroyNotify) g_object_unref);
-+}
-+
- static MetaDrmBuffer *
- copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
- MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state,
-@@ -681,6 +704,8 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen,
- return NULL;
- }
-
-+ reference_owning_gbm_surface (onscreen, buffer_gbm);
-+
- g_object_set_qdata_full (G_OBJECT (buffer_gbm),
- blit_source_quark,
- g_object_ref (primary_gpu_fb),
-@@ -693,12 +718,17 @@ static MetaDrmBufferDumb *
- secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state)
- {
- MetaDrmBufferDumb *current_dumb_fb;
-+ const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs);
-+ int i;
-
- current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb;
-- if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0])
-- return secondary_gpu_state->cpu.dumb_fbs[1];
-- else
-- return secondary_gpu_state->cpu.dumb_fbs[0];
-+ for (i = 0; i < n_dumb_fbs; i++)
-+ {
-+ if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i])
-+ return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs];
-+ }
-+
-+ return secondary_gpu_state->cpu.dumb_fbs[0];
- }
-
- static MetaDrmBuffer *
-@@ -1029,10 +1059,15 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback,
- g_warning ("Page flip failed: %s", error->message);
-
- frame_info = cogl_onscreen_peek_head_frame_info (onscreen);
-- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
-
-- meta_onscreen_native_notify_frame_complete (onscreen);
-- meta_onscreen_native_clear_next_fb (onscreen);
-+ /* After resuming from suspend, drop_stalled_swap might have done this
-+ * already and emptied the frame_info queue.
-+ */
-+ if (frame_info)
-+ {
-+ frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
-+ meta_onscreen_native_notify_frame_complete (onscreen);
-+ }
- }
-
- static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = {
-@@ -1053,30 +1088,35 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys;
- MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform;
- MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native;
-- MetaRenderer *renderer = META_RENDERER (renderer_native);
-- MetaBackend *backend = meta_renderer_get_backend (renderer);
-- MetaMonitorManager *monitor_manager =
-- meta_backend_get_monitor_manager (backend);
- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
- MetaGpuKms *render_gpu = onscreen_native->render_gpu;
- MetaDeviceFile *render_device_file;
- ClutterFrame *frame = user_data;
-- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
-- MetaKmsUpdate *kms_update;
- CoglOnscreenClass *parent_class;
- gboolean egl_context_changed = FALSE;
-- MetaPowerSave power_save_mode;
- g_autoptr (GError) error = NULL;
- MetaDrmBufferFlags buffer_flags;
- MetaDrmBufferGbm *buffer_gbm;
- g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL;
- g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL;
-- MetaKmsCrtc *kms_crtc;
-- MetaKmsDevice *kms_device;
-+ size_t rectangles_size;
-
- COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers,
- "Onscreen (swap-buffers)");
-
-+ 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,
-@@ -1113,6 +1153,7 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- return;
- }
-
-+ reference_owning_gbm_surface (onscreen, buffer_gbm);
- primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm));
- break;
- case META_RENDERER_NATIVE_MODE_SURFACELESS:
-@@ -1132,7 +1173,15 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- switch (renderer_gpu_data->mode)
- {
- case META_RENDERER_NATIVE_MODE_GBM:
-- g_warn_if_fail (onscreen_native->gbm.next_fb == NULL);
-+ if (onscreen_native->gbm.next_fb != NULL)
-+ {
-+ 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);
- else
-@@ -1146,6 +1195,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- #endif
- }
-
-+ clutter_frame_set_result (frame,
-+ CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
-+
- /*
- * If we changed EGL context, cogl will have the wrong idea about what is
- * current, making it fail to set it when it needs to. Avoid that by making
-@@ -1155,12 +1207,83 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- if (egl_context_changed)
- _cogl_winsys_egl_ensure_current (cogl_display);
-
-- kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc));
-- kms_device = meta_kms_crtc_get_device (kms_crtc);
-+ rectangles_size = n_rectangles * 4 * sizeof (int);
-+ onscreen_native->next_post.rectangles =
-+ g_realloc (onscreen_native->next_post.rectangles, rectangles_size);
-+ memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size);
-+ onscreen_native->next_post.n_rectangles = n_rectangles;
-+
-+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
-+ onscreen_native->next_post.frame = clutter_frame_ref (frame);
-+
-+ 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);
-+ MetaKms *kms = meta_backend_native_get_kms (backend_native);
-+ MetaMonitorManager *monitor_manager =
-+ meta_backend_get_monitor_manager (backend);
-+ MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
-+ MetaPowerSave power_save_mode;
-+ MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
-+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
-+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
-+ MetaKmsUpdate *kms_update;
-+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
-+ g_autoptr (ClutterFrame) frame = NULL;
-+ MetaFrameNative *frame_native;
-+
-+ if (onscreen_native->next_post.frame == NULL ||
-+ onscreen_native->view == NULL)
-+ return;
-+
-+ if (meta_kms_is_shutting_down (kms))
-+ {
-+ meta_onscreen_native_discard_pending_swaps (onscreen);
-+ return;
-+ }
-
- power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager);
- if (power_save_mode == META_POWER_SAVE_ON)
- {
-+ unsigned int frames_pending =
-+ cogl_onscreen_count_pending_frames (onscreen);
-+ unsigned int posts_pending;
-+
-+ g_assert (frames_pending >= onscreen_native->swaps_pending);
-+ posts_pending = frames_pending - onscreen_native->swaps_pending;
-+ if (posts_pending > 0)
-+ return; /* wait for the next frame notification and then try again */
-+
-+ frame = g_steal_pointer (&onscreen_native->next_post.frame);
-+ frame_native = meta_frame_native_from_frame (frame);
-+
-+ if (onscreen_native->swaps_pending == 0)
-+ {
-+ if (frame_native)
-+ {
-+ kms_update = meta_frame_native_steal_kms_update (frame_native);
-+ if (kms_update)
-+ post_finish_frame (onscreen_native, kms_update);
-+ }
-+ return;
-+ }
-+
-+ drop_stalled_swap (onscreen);
-+ onscreen_native->swaps_pending--;
-+
- kms_update = meta_frame_native_ensure_kms_update (frame_native,
- kms_device);
- meta_kms_update_add_result_listener (kms_update,
-@@ -1175,15 +1298,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- onscreen_native->crtc,
- kms_update,
- 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;
- }
-
-@@ -1203,8 +1324,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- kms_update = meta_frame_native_steal_kms_update (frame_native);
- meta_renderer_native_queue_mode_set_update (renderer_native,
- kms_update);
-- clutter_frame_set_result (frame,
-- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
- return;
- }
- else if (meta_renderer_native_has_pending_mode_set (renderer_native))
-@@ -1218,8 +1337,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
-
- meta_frame_native_steal_kms_update (frame_native);
- meta_renderer_native_post_mode_set_updates (renderer_native);
-- clutter_frame_set_result (frame,
-- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
- return;
- }
- break;
-@@ -1235,8 +1352,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- kms_update);
-
- meta_renderer_native_post_mode_set_updates (renderer_native);
-- clutter_frame_set_result (frame,
-- CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
- return;
- }
- break;
-@@ -1251,7 +1366,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen,
- kms_update = meta_frame_native_steal_kms_update (frame_native);
- meta_kms_device_post_update (kms_device, kms_update,
- META_KMS_UPDATE_FLAG_NONE);
-- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
- }
-
- gboolean
-@@ -1296,6 +1410,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
- CoglOnscreen *onscreen = COGL_ONSCREEN (onscreen_native);
- const GError *error;
- CoglFrameInfo *frame_info;
-+ g_autoptr (MetaDrmBuffer) direct_fb = g_steal_pointer (&onscreen_native->gbm.direct_fb);
-
- error = meta_kms_feedback_get_error (kms_feedback);
- if (!error)
-@@ -1309,8 +1424,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
-
- g_warning ("Direct scanout page flip failed: %s", error->message);
-
-- cogl_scanout_notify_failed (COGL_SCANOUT (onscreen_native->gbm.next_fb),
-- onscreen);
-+ cogl_scanout_notify_failed (COGL_SCANOUT (direct_fb), onscreen);
- clutter_stage_view_add_redraw_clip (view, NULL);
- clutter_stage_view_schedule_update_now (view);
- }
-@@ -1319,7 +1433,6 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback,
- frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC;
-
- meta_onscreen_native_notify_frame_complete (onscreen);
-- meta_onscreen_native_clear_next_fb (onscreen);
- }
-
- static const MetaKmsResultListenerVtable scanout_result_listener_vtable = {
-@@ -1371,6 +1484,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);
-
-@@ -1385,6 +1510,8 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen,
- kms_device = meta_kms_crtc_get_device (kms_crtc);
- kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device);
-
-+ g_set_object (&onscreen_native->gbm.direct_fb,
-+ onscreen_native->gbm.next_fb);
- meta_kms_update_add_result_listener (kms_update,
- &scanout_result_listener_vtable,
- NULL,
-@@ -1430,12 +1557,6 @@ void
- meta_onscreen_native_before_redraw (CoglOnscreen *onscreen,
- ClutterFrame *frame)
- {
-- MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen);
-- MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc);
-- MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms);
--
-- meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc),
-- kms_crtc);
- }
-
- void
-@@ -1555,22 +1676,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
- MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
- MetaFrameNative *frame_native = meta_frame_native_from_frame (frame);
- MetaKmsUpdate *kms_update;
-+ unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen);
-+ unsigned int swaps_pending = onscreen_native->swaps_pending;
-+ unsigned int posts_pending = frames_pending - swaps_pending;
-
-- kms_update = meta_frame_native_steal_kms_update (frame_native);
-- if (!kms_update)
-+ onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device,
-+ kms_crtc);
-+
-+ if (!meta_frame_native_has_kms_update (frame_native))
- {
-- if (meta_kms_device_handle_flush (kms_device, kms_crtc))
-- {
-- kms_update = meta_kms_update_new (kms_device);
-- meta_kms_update_set_flushing (kms_update, kms_crtc);
-- }
-- else
-+ if (!onscreen_native->needs_flush || posts_pending)
- {
- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
- return;
- }
- }
-
-+ if (posts_pending && !swaps_pending)
-+ {
-+ g_return_if_fail (meta_frame_native_has_kms_update (frame_native));
-+ g_warn_if_fail (onscreen_native->next_post.frame == NULL);
-+
-+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
-+ onscreen_native->next_post.frame = clutter_frame_ref (frame);
-+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
-+ return;
-+ }
-+
-+ kms_update = meta_frame_native_steal_kms_update (frame_native);
-+
-+ if (posts_pending && swaps_pending)
-+ {
-+ MetaFrameNative *older_frame_native;
-+ MetaKmsUpdate *older_kms_update;
-+
-+ g_return_if_fail (kms_update);
-+ g_return_if_fail (onscreen_native->next_post.frame != NULL);
-+
-+ older_frame_native =
-+ meta_frame_native_from_frame (onscreen_native->next_post.frame);
-+ older_kms_update =
-+ meta_frame_native_ensure_kms_update (older_frame_native, kms_device);
-+ meta_kms_update_merge_from (older_kms_update, kms_update);
-+ meta_kms_update_free (kms_update);
-+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE);
-+ return;
-+ }
-+
-+ if (!kms_update)
-+ {
-+ kms_update = meta_kms_update_new (kms_device);
-+ g_warn_if_fail (onscreen_native->needs_flush);
-+ }
-+
-+ if (onscreen_native->needs_flush)
-+ {
-+ meta_kms_update_set_flushing (kms_update, kms_crtc);
-+ onscreen_native->needs_flush = FALSE;
-+ }
-+
-+ post_finish_frame (onscreen_native, kms_update);
-+
-+ clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
-+}
-+
-+static void
-+post_finish_frame (MetaOnscreenNative *onscreen_native,
-+ MetaKmsUpdate *kms_update)
-+{
-+ MetaCrtc *crtc = onscreen_native->crtc;
-+ MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc));
-+ MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc);
-+ g_autoptr (MetaKmsFeedback) kms_feedback = NULL;
-+
- meta_kms_update_add_result_listener (kms_update,
- &finish_frame_result_listener_vtable,
- NULL,
-@@ -1594,7 +1772,17 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen,
- meta_kms_update_set_flushing (kms_update, kms_crtc);
- meta_kms_device_post_update (kms_device, kms_update,
- META_KMS_UPDATE_FLAG_NONE);
-- clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
-+}
-+
-+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
-@@ -2421,7 +2609,7 @@ meta_onscreen_native_dispose (GObject *object)
- {
- case META_RENDERER_NATIVE_MODE_GBM:
- g_clear_object (&onscreen_native->gbm.next_fb);
-- free_current_bo (onscreen);
-+ g_clear_object (&onscreen_native->gbm.direct_fb);
- break;
- case META_RENDERER_NATIVE_MODE_SURFACELESS:
- g_assert_not_reached ();
-@@ -2455,6 +2643,10 @@ meta_onscreen_native_dispose (GObject *object)
-
- g_clear_object (&onscreen_native->output);
- g_clear_object (&onscreen_native->crtc);
-+
-+ g_clear_pointer (&onscreen_native->next_post.rectangles, g_free);
-+ g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref);
-+ onscreen_native->next_post.n_rectangles = 0;
- }
-
- static void
-diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h
-index 91eb7b533..11bb5ba56 100644
---- a/src/backends/native/meta-onscreen-native.h
-+++ b/src/backends/native/meta-onscreen-native.h
-@@ -45,6 +45,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 e6c653e26..7e39889bc 100644
---- a/src/backends/native/meta-renderer-native.c
-+++ b/src/backends/native/meta-renderer-native.c
-@@ -99,6 +99,7 @@ struct _MetaRendererNative
-
- GList *detached_onscreens;
- GList *lingering_onscreens;
-+ GList *disabled_crtcs;
- guint release_unused_gpus_idle_id;
-
- GList *power_save_page_flip_onscreens;
-@@ -683,6 +684,9 @@ configure_disabled_crtcs (MetaKmsDevice *kms_device,
-
- kms_update = ensure_mode_set_update (renderer_native, kms_device);
- meta_kms_update_mode_set (kms_update, kms_crtc, NULL, NULL);
-+
-+ renderer_native->disabled_crtcs =
-+ g_list_prepend (renderer_native->disabled_crtcs, kms_crtc);
- }
- }
-
-@@ -690,12 +694,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;
-@@ -707,6 +717,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 =
-@@ -817,6 +830,22 @@ clear_detached_onscreens (MetaRendererNative *renderer_native)
- g_object_unref);
- }
-
-+static void
-+clear_disabled_crtcs (MetaRendererNative *renderer_native)
-+{
-+ GList *l;
-+
-+ for (l = renderer_native->disabled_crtcs; l; l = l->next)
-+ {
-+ MetaKmsCrtc *kms_crtc = l->data;
-+ MetaSwapChain *swap_chain = meta_kms_crtc_get_swap_chain (kms_crtc);
-+
-+ meta_swap_chain_release_buffers (swap_chain);
-+ }
-+
-+ g_clear_list (&renderer_native->disabled_crtcs, NULL);
-+}
-+
- static void
- mode_sets_update_result_feedback (const MetaKmsFeedback *kms_feedback,
- gpointer user_data)
-@@ -878,6 +907,7 @@ meta_renderer_native_post_mode_set_updates (MetaRendererNative *renderer_native)
- post_mode_set_updates (renderer_native);
-
- clear_detached_onscreens (renderer_native);
-+ clear_disabled_crtcs (renderer_native);
-
- meta_kms_notify_modes_set (kms);
-
-@@ -1467,6 +1497,26 @@ detach_onscreens (MetaRenderer *renderer)
- }
- }
-
-+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
- meta_renderer_native_rebuild_views (MetaRenderer *renderer)
- {
-@@ -1477,6 +1527,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);
- g_hash_table_remove_all (renderer_native->mode_set_updates);
-
-@@ -2239,6 +2290,7 @@ meta_renderer_native_finalize (GObject *object)
- g_clear_handle_id (&renderer_native->release_unused_gpus_idle_id,
- g_source_remove);
- clear_detached_onscreens (renderer_native);
-+ clear_disabled_crtcs (renderer_native);
-
- g_hash_table_destroy (renderer_native->gpu_datas);
- g_clear_object (&renderer_native->gles3);
-diff --git a/src/backends/native/meta-swap-chain.c b/src/backends/native/meta-swap-chain.c
-new file mode 100644
-index 000000000..c3bed569d
---- /dev/null
-+++ b/src/backends/native/meta-swap-chain.c
-@@ -0,0 +1,149 @@
-+/*
-+ * Copyright (C) 2022 Canonical Ltd.
-+ *
-+ * This program is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU General Public License as
-+ * published by the Free Software Foundation; either version 2 of the
-+ * License, or (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful, but
-+ * WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-+ * 02111-1307, USA.
-+ *
-+ * Author: Daniel van Vugt <daniel.van.vugt@canonical.com>
-+ */
-+
-+#include "backends/native/meta-swap-chain.h"
-+
-+typedef struct
-+{
-+ GObject *front, *back;
-+ gboolean back_is_set;
-+} PlaneState;
-+
-+typedef struct _MetaSwapChainPrivate MetaSwapChainPrivate;
-+struct _MetaSwapChainPrivate
-+{
-+ GHashTable *plane_states;
-+};
-+
-+G_DEFINE_TYPE_WITH_PRIVATE (MetaSwapChain, meta_swap_chain, G_TYPE_OBJECT)
-+
-+MetaSwapChain *
-+meta_swap_chain_new (void)
-+{
-+ return g_object_new (META_TYPE_SWAP_CHAIN, NULL);
-+}
-+
-+void
-+meta_swap_chain_push_buffer (MetaSwapChain *swap_chain,
-+ unsigned int plane_id,
-+ GObject *buffer)
-+{
-+ MetaSwapChainPrivate *priv =
-+ meta_swap_chain_get_instance_private (swap_chain);
-+ gpointer key = GUINT_TO_POINTER (plane_id);
-+ PlaneState *plane_state;
-+
-+ plane_state = g_hash_table_lookup (priv->plane_states, key);
-+ if (plane_state == NULL)
-+ {
-+ plane_state = g_new0 (PlaneState, 1);
-+ g_hash_table_insert (priv->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_swap_chain_swap_buffers (MetaSwapChain *swap_chain)
-+{
-+ MetaSwapChainPrivate *priv =
-+ meta_swap_chain_get_instance_private (swap_chain);
-+
-+ g_hash_table_foreach (priv->plane_states, swap_plane_buffers, NULL);
-+}
-+
-+void
-+meta_swap_chain_release_buffers (MetaSwapChain *swap_chain)
-+{
-+ MetaSwapChainPrivate *priv =
-+ meta_swap_chain_get_instance_private (swap_chain);
-+
-+ g_hash_table_remove_all (priv->plane_states);
-+}
-+
-+static void
-+meta_swap_chain_dispose (GObject *object)
-+{
-+ MetaSwapChain *swap_chain = META_SWAP_CHAIN (object);
-+
-+ meta_swap_chain_release_buffers (swap_chain);
-+
-+ G_OBJECT_CLASS (meta_swap_chain_parent_class)->dispose (object);
-+}
-+
-+static void
-+meta_swap_chain_finalize (GObject *object)
-+{
-+ MetaSwapChain *swap_chain = META_SWAP_CHAIN (object);
-+ MetaSwapChainPrivate *priv =
-+ meta_swap_chain_get_instance_private (swap_chain);
-+
-+ g_hash_table_unref (priv->plane_states);
-+
-+ G_OBJECT_CLASS (meta_swap_chain_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_swap_chain_init (MetaSwapChain *swap_chain)
-+{
-+ MetaSwapChainPrivate *priv =
-+ meta_swap_chain_get_instance_private (swap_chain);
-+
-+ priv->plane_states = g_hash_table_new_full (NULL,
-+ NULL,
-+ NULL,
-+ destroy_plane_state);
-+}
-+
-+static void
-+meta_swap_chain_class_init (MetaSwapChainClass *klass)
-+{
-+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
-+
-+ object_class->dispose = meta_swap_chain_dispose;
-+ object_class->finalize = meta_swap_chain_finalize;
-+}
-diff --git a/src/backends/native/meta-swap-chain.h b/src/backends/native/meta-swap-chain.h
-new file mode 100644
-index 000000000..bad772b89
---- /dev/null
-+++ b/src/backends/native/meta-swap-chain.h
-@@ -0,0 +1,48 @@
-+/*
-+ * Copyright (C) 2022 Canonical Ltd.
-+ *
-+ * This program is free software; you can redistribute it and/or
-+ * modify it under the terms of the GNU General Public License as
-+ * published by the Free Software Foundation; either version 2 of the
-+ * License, or (at your option) any later version.
-+ *
-+ * This program is distributed in the hope that it will be useful, but
-+ * WITHOUT ANY WARRANTY; without even the implied warranty of
-+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-+ * General Public License for more details.
-+ *
-+ * You should have received a copy of the GNU General Public License
-+ * along with this program; if not, write to the Free Software
-+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
-+ * 02111-1307, USA.
-+ *
-+ * Author: Daniel van Vugt <daniel.van.vugt@canonical.com>
-+ */
-+
-+#ifndef META_SWAP_CHAIN_H
-+#define META_SWAP_CHAIN_H
-+
-+#include <glib-object.h>
-+
-+#define META_TYPE_SWAP_CHAIN (meta_swap_chain_get_type ())
-+G_DECLARE_DERIVABLE_TYPE (MetaSwapChain,
-+ meta_swap_chain,
-+ META, SWAP_CHAIN,
-+ GObject)
-+
-+struct _MetaSwapChainClass
-+{
-+ GObjectClass parent_class;
-+};
-+
-+MetaSwapChain * meta_swap_chain_new (void);
-+
-+void meta_swap_chain_push_buffer (MetaSwapChain *swap_chain,
-+ unsigned int plane_id,
-+ GObject *buffer);
-+
-+void meta_swap_chain_swap_buffers (MetaSwapChain *swap_chain);
-+
-+void meta_swap_chain_release_buffers (MetaSwapChain *swap_chain);
-+
-+#endif /* META_SWAP_CHAIN_H */
-diff --git a/src/meson.build b/src/meson.build
-index ca2ef166c..0038988bd 100644
---- a/src/meson.build
-+++ b/src/meson.build
-@@ -850,6 +850,8 @@ if have_native_backend
- 'backends/native/meta-seat-native.h',
- 'backends/native/meta-stage-native.c',
- 'backends/native/meta-stage-native.h',
-+ 'backends/native/meta-swap-chain.c',
-+ 'backends/native/meta-swap-chain.h',
- 'backends/native/meta-thread-impl.c',
- 'backends/native/meta-thread-impl.h',
- 'backends/native/meta-thread-private.h',
-diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c
-index 90ea9b581..aafa682bd 100644
---- a/src/tests/native-kms-render.c
-+++ b/src/tests/native-kms-render.c
-@@ -39,6 +39,8 @@
- #include "tests/meta-wayland-test-driver.h"
- #include "tests/meta-wayland-test-utils.h"
-
-+#define N_FRAMES_PER_TEST 30
-+
- typedef struct
- {
- int number_of_frames_left;
-@@ -46,12 +48,15 @@ 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;
-
- struct {
-+ int scanouts_attempted;
- gboolean scanout_sabotaged;
- gboolean fallback_painted;
- guint repaint_guard_id;
-@@ -101,7 +106,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",
-@@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage,
- KmsRenderingTest *test)
- {
- test->scanout.n_paints = 0;
-- test->scanout.fb_id = 0;
- }
-
- static void
-@@ -134,6 +138,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)
-@@ -141,8 +146,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
-@@ -171,12 +182,12 @@ on_scanout_presented (ClutterStage *stage,
- MetaDeviceFile *device_file;
- GError *error = NULL;
- drmModeCrtc *drm_crtc;
-+ uint32_t first_fb_id_expected;
-
-- if (test->wait_for_scanout && 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);
-
-@@ -195,15 +206,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
-@@ -242,7 +279,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,
- };
-
-@@ -268,7 +307,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");
-@@ -290,10 +330,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);
-@@ -305,10 +350,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);
-@@ -362,6 +412,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage,
- if (!scanout)
- return;
-
-+ test->scanout_fallback.scanouts_attempted++;
-+
-+ /* The first scanout candidate frame will get composited due to triple
-+ * buffering draining the queue to drop to double buffering. So don't
-+ * sabotage that first frame.
-+ */
-+ if (test->scanout_fallback.scanouts_attempted < 2)
-+ return;
-+
- g_assert_false (test->scanout_fallback.scanout_sabotaged);
-
- if (is_atomic_mode_setting (kms_device))
-@@ -399,6 +458,15 @@ on_scanout_fallback_paint_view (ClutterStage *stage,
- g_clear_handle_id (&test->scanout_fallback.repaint_guard_id,
- g_source_remove);
- test->scanout_fallback.fallback_painted = TRUE;
-+ test->scanout_fallback.scanout_sabotaged = FALSE;
-+ }
-+ else if (test->scanout_fallback.scanouts_attempted == 1)
-+ {
-+ /* Now that we've seen the first scanout attempt that was inhibited by
-+ * triple buffering, try a second frame. The second one should scanout
-+ * and will be sabotaged.
-+ */
-+ clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
- }
- }
-
-@@ -408,11 +476,11 @@ on_scanout_fallback_presented (ClutterStage *stage,
- ClutterFrameInfo *frame_info,
- KmsRenderingTest *test)
- {
-- if (!test->scanout_fallback.scanout_sabotaged)
-- return;
-+ if (test->scanout_fallback.fallback_painted)
-+ g_main_loop_quit (test->loop);
-
-- g_assert_true (test->scanout_fallback.fallback_painted);
-- g_main_loop_quit (test->loop);
-+ test->number_of_frames_left--;
-+ g_assert_cmpint (test->number_of_frames_left, >, 0);
- }
-
- static void
-@@ -441,6 +509,7 @@ meta_test_kms_render_client_scanout_fallback (void)
- g_assert_nonnull (wayland_test_client);
-
- test = (KmsRenderingTest) {
-+ .number_of_frames_left = N_FRAMES_PER_TEST,
- .loop = g_main_loop_new (NULL, FALSE),
- };
-