diff options
author | ohno1052 | 2024-02-16 14:33:44 +0500 |
---|---|---|
committer | ohno1052 | 2024-02-16 14:33:44 +0500 |
commit | c5da8df6d1241b5bcb29d9cbb6d0253ce41e699f (patch) | |
tree | c885027b9f0eda5d1d0c43adda893e355cd602b4 | |
parent | 5ea4c58e8a9fb3d2d356c9f4609ce19415b69dc6 (diff) | |
download | aur-mutter-auto-rotation.tar.gz |
45.4
-rw-r--r-- | .SRCINFO | 6 | ||||
-rw-r--r-- | PKGBUILD | 7 | ||||
-rw-r--r-- | mr1441.patch | 2312 |
3 files changed, 4 insertions, 2321 deletions
@@ -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 @@ -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), - }; - |