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