Author: Daniel van Vugt Source: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441 Commit: 0b896518b2028d9c4d6ea44806d093fd33793689 Rebase: Fri Dec 8 16:31:18 2023 +0800 diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index ab493e0b0..905d8c926 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -35,6 +35,15 @@ enum static guint signals[N_SIGNALS]; +typedef enum +{ + TRIPLE_BUFFERING_MODE_NEVER, + TRIPLE_BUFFERING_MODE_AUTO, + TRIPLE_BUFFERING_MODE_ALWAYS, +} TripleBufferingMode; + +static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; + #define SYNC_DELAY_FALLBACK_FRACTION 0.875 typedef struct _ClutterFrameListener @@ -55,8 +64,9 @@ typedef enum _ClutterFrameClockState CLUTTER_FRAME_CLOCK_STATE_INIT, CLUTTER_FRAME_CLOCK_STATE_IDLE, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, - CLUTTER_FRAME_CLOCK_STATE_DISPATCHING, - CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED, + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE, + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED, + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, } ClutterFrameClockState; struct _ClutterFrameClock @@ -73,6 +83,7 @@ struct _ClutterFrameClock ClutterFrameClockState state; int64_t last_dispatch_time_us; + int64_t prev_last_dispatch_time_us; int64_t last_dispatch_lateness_us; int64_t last_presentation_time_us; int64_t next_update_time_us; @@ -87,6 +98,9 @@ struct _ClutterFrameClock int64_t vblank_duration_us; /* Last KMS buffer submission time. */ int64_t last_flip_time_us; + int64_t prev_last_flip_time_us; + + ClutterFrameHint last_flip_hints; /* Last time we promoted short-term maximum to long-term one */ int64_t longterm_promotion_us; @@ -219,10 +233,6 @@ static void maybe_update_longterm_max_duration_us (ClutterFrameClock *frame_clock, ClutterFrameInfo *frame_info) { - /* Do not update long-term max if there has been no measurement */ - if (!frame_clock->shortterm_max_update_duration_us) - return; - if ((frame_info->presentation_time - frame_clock->longterm_promotion_us) < G_USEC_PER_SEC) return; @@ -249,6 +259,12 @@ void clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, ClutterFrameInfo *frame_info) { +#ifdef CLUTTER_ENABLE_DEBUG + const char *debug_state = + frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO ? + "Triple buffering" : "Double buffering"; +#endif + COGL_TRACE_BEGIN_SCOPED (ClutterFrameClockNotifyPresented, "Frame Clock (presented)"); @@ -328,31 +344,58 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, frame_clock->got_measurements_last_frame = FALSE; - if (frame_info->cpu_time_before_buffer_swap_us != 0) + if (frame_info->cpu_time_before_buffer_swap_us != 0 || + frame_clock->ever_got_measurements) { int64_t dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us; + int64_t dispatch_time_us = 0, flip_time_us = 0; - dispatch_to_swap_us = - frame_info->cpu_time_before_buffer_swap_us - - frame_clock->last_dispatch_time_us; + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + g_warn_if_reached (); + G_GNUC_FALLTHROUGH; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + dispatch_time_us = frame_clock->last_dispatch_time_us; + flip_time_us = frame_clock->last_flip_time_us; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + dispatch_time_us = frame_clock->prev_last_dispatch_time_us; + flip_time_us = frame_clock->prev_last_flip_time_us; + break; + } + + if (frame_info->cpu_time_before_buffer_swap_us == 0) + { + /* Cursor-only updates with no "swap" or "flip" */ + dispatch_to_swap_us = 0; + swap_to_flip_us = 0; + } + else + { + dispatch_to_swap_us = frame_info->cpu_time_before_buffer_swap_us - + dispatch_time_us; + swap_to_flip_us = flip_time_us - + frame_info->cpu_time_before_buffer_swap_us; + } swap_to_rendering_done_us = frame_info->gpu_rendering_duration_ns / 1000; - swap_to_flip_us = - frame_clock->last_flip_time_us - - frame_info->cpu_time_before_buffer_swap_us; CLUTTER_NOTE (FRAME_TIMINGS, - "update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", + "%s: update2dispatch %ld µs, dispatch2swap %ld µs, swap2render %ld µs, swap2flip %ld µs", + debug_state, frame_clock->last_dispatch_lateness_us, dispatch_to_swap_us, swap_to_rendering_done_us, swap_to_flip_us); frame_clock->shortterm_max_update_duration_us = - CLAMP (frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + - MAX (swap_to_rendering_done_us, swap_to_flip_us), - frame_clock->shortterm_max_update_duration_us, - frame_clock->refresh_interval_us); + MAX (frame_clock->shortterm_max_update_duration_us, + frame_clock->last_dispatch_lateness_us + dispatch_to_swap_us + + MAX (swap_to_rendering_done_us, swap_to_flip_us)); maybe_update_longterm_max_duration_us (frame_clock, frame_info); @@ -361,7 +404,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, } else { - CLUTTER_NOTE (FRAME_TIMINGS, "update2dispatch %ld µs", + CLUTTER_NOTE (FRAME_TIMINGS, "%s: update2dispatch %ld µs", + debug_state, frame_clock->last_dispatch_lateness_us); } @@ -378,11 +422,18 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: g_warn_if_reached (); break; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; maybe_reschedule_update (frame_clock); break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + maybe_reschedule_update (frame_clock); + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + maybe_reschedule_update (frame_clock); + break; } } @@ -398,11 +449,18 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: g_warn_if_reached (); break; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; maybe_reschedule_update (frame_clock); break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + maybe_reschedule_update (frame_clock); + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + maybe_reschedule_update (frame_clock); + break; } } @@ -417,7 +475,14 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) if (!frame_clock->ever_got_measurements || G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_DYNAMIC_MAX_RENDER_TIME)) - return refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; + { + int64_t ret = refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION; + + if (frame_clock->state == CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE) + ret += refresh_interval_us; + + return ret; + } /* Max render time shows how early the frame clock needs to be dispatched * to make it to the predicted next presentation time. It is an estimate of @@ -437,8 +502,6 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) frame_clock->vblank_duration_us + clutter_max_render_time_constant_us; - max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); - return max_render_time_us; } @@ -453,8 +516,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, int64_t refresh_interval_us; int64_t min_render_time_allowed_us; int64_t max_render_time_allowed_us; - int64_t next_presentation_time_us; + int64_t next_presentation_time_us = 0; int64_t next_update_time_us; + gboolean skipped_frames = FALSE; now_us = g_get_monotonic_time (); @@ -498,7 +562,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, * */ last_presentation_time_us = frame_clock->last_presentation_time_us; - next_presentation_time_us = last_presentation_time_us + refresh_interval_us; + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + next_presentation_time_us = last_presentation_time_us + + refresh_interval_us; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + next_presentation_time_us = last_presentation_time_us + + 2 * refresh_interval_us; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + next_presentation_time_us = last_presentation_time_us + + 3 * refresh_interval_us; + break; + } /* * However, the last presentation could have happened more than a frame ago. @@ -534,6 +615,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, current_phase_us = (now_us - last_presentation_time_us) % refresh_interval_us; next_presentation_time_us = now_us - current_phase_us + refresh_interval_us; + skipped_frames = TRUE; } if (frame_clock->is_next_presentation_time_valid) @@ -566,7 +648,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, } } - if (next_presentation_time_us != last_presentation_time_us + refresh_interval_us) + if (skipped_frames) { /* There was an idle period since the last presentation, so there seems * be no constantly updating actor. In this case it's best to start @@ -607,8 +689,12 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) frame_clock->pending_reschedule = TRUE; frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; break; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->pending_reschedule = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: break; } @@ -645,9 +731,15 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: next_update_time_us = g_get_monotonic_time (); + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + next_update_time_us = g_get_monotonic_time (); + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; break; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: frame_clock->pending_reschedule = TRUE; frame_clock->pending_reschedule_now = TRUE; return; @@ -657,7 +749,6 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) frame_clock->next_update_time_us = next_update_time_us; g_source_set_ready_time (frame_clock->source, next_update_time_us); - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; frame_clock->is_next_presentation_time_valid = FALSE; } @@ -665,6 +756,12 @@ void clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) { int64_t next_update_time_us = -1; + TripleBufferingMode current_mode = triple_buffering_mode; + + if (current_mode == TRIPLE_BUFFERING_MODE_AUTO && + (frame_clock->last_flip_hints & + CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED)) + current_mode = TRIPLE_BUFFERING_MODE_NEVER; if (frame_clock->inhibit_count > 0) { @@ -676,6 +773,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) { case CLUTTER_FRAME_CLOCK_STATE_INIT: next_update_time_us = g_get_monotonic_time (); + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; break; case CLUTTER_FRAME_CLOCK_STATE_IDLE: calculate_next_update_time_us (frame_clock, @@ -684,11 +782,37 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) &frame_clock->min_render_time_allowed_us); frame_clock->is_next_presentation_time_valid = (frame_clock->next_presentation_time_us != 0); + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; break; case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: return; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + switch (current_mode) + { + case TRIPLE_BUFFERING_MODE_NEVER: + frame_clock->pending_reschedule = TRUE; + return; + case TRIPLE_BUFFERING_MODE_AUTO: + calculate_next_update_time_us (frame_clock, + &next_update_time_us, + &frame_clock->next_presentation_time_us, + &frame_clock->min_render_time_allowed_us); + frame_clock->is_next_presentation_time_valid = + (frame_clock->next_presentation_time_us != 0); + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; + break; + case TRIPLE_BUFFERING_MODE_ALWAYS: + next_update_time_us = g_get_monotonic_time (); + frame_clock->next_presentation_time_us = 0; + frame_clock->is_next_presentation_time_valid = FALSE; + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; + break; + } + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: frame_clock->pending_reschedule = TRUE; return; } @@ -697,7 +821,6 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) frame_clock->next_update_time_us = next_update_time_us; g_source_set_ready_time (frame_clock->source, next_update_time_us); - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; } static void @@ -728,7 +851,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, frame_clock->refresh_interval_us; lateness_us = time_us - ideal_dispatch_time_us; - if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us) + if (lateness_us < 0 || lateness_us >= frame_clock->refresh_interval_us / 4) frame_clock->last_dispatch_lateness_us = 0; else frame_clock->last_dispatch_lateness_us = lateness_us; @@ -749,10 +872,25 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, } #endif + frame_clock->prev_last_dispatch_time_us = frame_clock->last_dispatch_time_us; frame_clock->last_dispatch_time_us = time_us; g_source_set_ready_time (frame_clock->source, -1); - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHING; + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + g_warn_if_reached (); + return; + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; + break; + } frame_count = frame_clock->frame_count++; @@ -781,25 +919,31 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock, result = iface->frame (frame_clock, frame, frame_clock->listener.user_data); COGL_TRACE_END (ClutterFrameClockFrame); - switch (frame_clock->state) + switch (result) { - case CLUTTER_FRAME_CLOCK_STATE_INIT: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: - g_warn_if_reached (); - break; - case CLUTTER_FRAME_CLOCK_STATE_IDLE: - case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: break; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - switch (result) + case CLUTTER_FRAME_RESULT_IDLE: + /* The frame was aborted; nothing to paint/present */ + switch (frame_clock->state) { - case CLUTTER_FRAME_RESULT_PENDING_PRESENTED: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED; + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + g_warn_if_reached (); break; - case CLUTTER_FRAME_RESULT_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE; maybe_reschedule_update (frame_clock); break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; + maybe_reschedule_update (frame_clock); + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + maybe_reschedule_update (frame_clock); + break; } break; } @@ -832,10 +976,13 @@ frame_clock_source_dispatch (GSource *source, } void -clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, - int64_t flip_time_us) +clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, + int64_t flip_time_us, + ClutterFrameHint hints) { + frame_clock->prev_last_flip_time_us = frame_clock->last_flip_time_us; frame_clock->last_flip_time_us = flip_time_us; + frame_clock->last_flip_hints = hints; } GString * @@ -929,8 +1076,6 @@ clutter_frame_clock_dispose (GObject *object) { ClutterFrameClock *frame_clock = CLUTTER_FRAME_CLOCK (object); - g_warn_if_fail (frame_clock->state != CLUTTER_FRAME_CLOCK_STATE_DISPATCHING); - if (frame_clock->source) { g_signal_emit (frame_clock, signals[DESTROY], 0); @@ -951,6 +1096,15 @@ static void clutter_frame_clock_class_init (ClutterFrameClockClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + const char *mode_str; + + mode_str = g_getenv ("MUTTER_DEBUG_TRIPLE_BUFFERING"); + if (!g_strcmp0 (mode_str, "never")) + triple_buffering_mode = TRIPLE_BUFFERING_MODE_NEVER; + else if (!g_strcmp0 (mode_str, "auto")) + triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; + else if (!g_strcmp0 (mode_str, "always")) + triple_buffering_mode = TRIPLE_BUFFERING_MODE_ALWAYS; object_class->dispose = clutter_frame_clock_dispose; diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h index 93ebc9438..e1fd6b986 100644 --- a/clutter/clutter/clutter-frame-clock.h +++ b/clutter/clutter/clutter-frame-clock.h @@ -33,6 +33,12 @@ typedef enum _ClutterFrameResult CLUTTER_FRAME_RESULT_IDLE, } ClutterFrameResult; +typedef enum _ClutterFrameHint +{ + CLUTTER_FRAME_HINT_NONE = 0, + CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED = 1 << 0, +} ClutterFrameHint; + #define CLUTTER_TYPE_FRAME_CLOCK (clutter_frame_clock_get_type ()) CLUTTER_EXPORT G_DECLARE_FINAL_TYPE (ClutterFrameClock, clutter_frame_clock, @@ -91,7 +97,8 @@ void clutter_frame_clock_remove_timeline (ClutterFrameClock *frame_clock, CLUTTER_EXPORT float clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock); -void clutter_frame_clock_record_flip_time (ClutterFrameClock *frame_clock, - int64_t flip_time_us); +void clutter_frame_clock_record_flip (ClutterFrameClock *frame_clock, + int64_t flip_time_us, + ClutterFrameHint hints); GString * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock); diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h index 0a0226b0a..55c76df72 100644 --- a/clutter/clutter/clutter-frame-private.h +++ b/clutter/clutter/clutter-frame-private.h @@ -34,6 +34,7 @@ struct _ClutterFrame gboolean has_result; ClutterFrameResult result; + ClutterFrameHint hints; }; CLUTTER_EXPORT diff --git a/clutter/clutter/clutter-frame.c b/clutter/clutter/clutter-frame.c index 85baef274..413ce9c2b 100644 --- a/clutter/clutter/clutter-frame.c +++ b/clutter/clutter/clutter-frame.c @@ -113,3 +113,16 @@ clutter_frame_set_result (ClutterFrame *frame, frame->result = result; frame->has_result = TRUE; } + +void +clutter_frame_set_hint (ClutterFrame *frame, + ClutterFrameHint hint) +{ + frame->hints |= hint; +} + +ClutterFrameHint +clutter_frame_get_hints (ClutterFrame *frame) +{ + return frame->hints; +} diff --git a/clutter/clutter/clutter-frame.h b/clutter/clutter/clutter-frame.h index 1d5660d68..0e7f618a4 100644 --- a/clutter/clutter/clutter-frame.h +++ b/clutter/clutter/clutter-frame.h @@ -54,4 +54,11 @@ void clutter_frame_set_result (ClutterFrame *frame, CLUTTER_EXPORT gboolean clutter_frame_has_result (ClutterFrame *frame); +CLUTTER_EXPORT +void clutter_frame_set_hint (ClutterFrame *frame, + ClutterFrameHint hint); + +CLUTTER_EXPORT +ClutterFrameHint clutter_frame_get_hints (ClutterFrame *frame); + G_DEFINE_AUTOPTR_CLEANUP_FUNC (ClutterFrame, clutter_frame_unref) diff --git a/clutter/clutter/clutter-stage-view.c b/clutter/clutter/clutter-stage-view.c index 168746dd4..f0e36619e 100644 --- a/clutter/clutter/clutter-stage-view.c +++ b/clutter/clutter/clutter-stage-view.c @@ -1264,14 +1264,21 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock, _clutter_stage_window_redraw_view (stage_window, view, frame); - clutter_frame_clock_record_flip_time (frame_clock, - g_get_monotonic_time ()); + clutter_frame_clock_record_flip (frame_clock, + g_get_monotonic_time (), + clutter_frame_get_hints (frame)); clutter_stage_emit_after_paint (stage, view, frame); if (_clutter_context_get_show_fps ()) end_frame_timing_measurement (view); } + else + { + clutter_frame_clock_record_flip (frame_clock, + g_get_monotonic_time (), + clutter_frame_get_hints (frame)); + } _clutter_stage_window_finish_frame (stage_window, view, frame); diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h index 9dbecfd0c..681d91d2b 100644 --- a/cogl/cogl/cogl-onscreen-private.h +++ b/cogl/cogl/cogl-onscreen-private.h @@ -95,3 +95,6 @@ cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); COGL_EXPORT CoglFrameInfo * cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); + +COGL_EXPORT unsigned int +cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen); diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c index 73425e498..02c4474b2 100644 --- a/cogl/cogl/cogl-onscreen.c +++ b/cogl/cogl/cogl-onscreen.c @@ -508,6 +508,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen) return g_queue_pop_head (&priv->pending_frame_infos); } +unsigned int +cogl_onscreen_count_pending_frames (CoglOnscreen *onscreen) +{ + CoglOnscreenPrivate *priv = cogl_onscreen_get_instance_private (onscreen); + + return g_queue_get_length (&priv->pending_frame_infos); +} + CoglFrameClosure * cogl_onscreen_add_frame_callback (CoglOnscreen *onscreen, CoglFrameCallback callback, diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c index c35cb36e3..2130e4042 100644 --- a/src/backends/meta-stage-impl.c +++ b/src/backends/meta-stage-impl.c @@ -775,6 +775,8 @@ meta_stage_impl_redraw_view (ClutterStageWindow *stage_window, { g_autoptr (GError) error = NULL; + clutter_frame_set_hint (frame, CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); + if (meta_stage_impl_scanout_view (stage_impl, stage_view, scanout, diff --git a/src/backends/native/meta-kms-crtc.c b/src/backends/native/meta-kms-crtc.c index d89b12598..b17e8460d 100644 --- a/src/backends/native/meta-kms-crtc.c +++ b/src/backends/native/meta-kms-crtc.c @@ -48,6 +48,8 @@ struct _MetaKmsCrtc MetaKmsCrtcState current_state; MetaKmsCrtcPropTable prop_table; + + MetaSwapChain *swap_chain; }; G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT) @@ -99,6 +101,12 @@ meta_kms_crtc_get_prop_drm_value (MetaKmsCrtc *crtc, return meta_kms_prop_convert_value (prop, value); } +MetaSwapChain * +meta_kms_crtc_get_swap_chain (MetaKmsCrtc *crtc) +{ + return crtc->swap_chain; +} + gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc) { @@ -465,12 +473,23 @@ meta_kms_crtc_new (MetaKmsImplDevice *impl_device, return crtc; } +static void +meta_kms_crtc_dispose (GObject *object) +{ + MetaKmsCrtc *crtc = META_KMS_CRTC (object); + + meta_swap_chain_release_buffers (crtc->swap_chain); + + G_OBJECT_CLASS (meta_kms_crtc_parent_class)->dispose (object); +} + static void meta_kms_crtc_finalize (GObject *object) { MetaKmsCrtc *crtc = META_KMS_CRTC (object); g_clear_pointer (&crtc->current_state.gamma.value, meta_gamma_lut_free); + g_clear_object (&crtc->swap_chain); G_OBJECT_CLASS (meta_kms_crtc_parent_class)->finalize (object); } @@ -480,6 +499,7 @@ meta_kms_crtc_init (MetaKmsCrtc *crtc) { crtc->current_state.gamma.size = 0; crtc->current_state.gamma.value = NULL; + crtc->swap_chain = meta_swap_chain_new (); } static void @@ -487,6 +507,7 @@ meta_kms_crtc_class_init (MetaKmsCrtcClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->dispose = meta_kms_crtc_dispose; object_class->finalize = meta_kms_crtc_finalize; } diff --git a/src/backends/native/meta-kms-crtc.h b/src/backends/native/meta-kms-crtc.h index b26b682dd..a30a6de6e 100644 --- a/src/backends/native/meta-kms-crtc.h +++ b/src/backends/native/meta-kms-crtc.h @@ -22,6 +22,7 @@ #include #include "backends/native/meta-kms-types.h" +#include "backends/native/meta-swap-chain.h" #include "backends/meta-backend-types.h" #include "core/util-private.h" #include "meta/boxes.h" @@ -60,3 +61,5 @@ int meta_kms_crtc_get_idx (MetaKmsCrtc *crtc); META_EXPORT_TEST gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc); + +MetaSwapChain * meta_kms_crtc_get_swap_chain (MetaKmsCrtc *crtc); diff --git a/src/backends/native/meta-kms-impl-device-atomic.c b/src/backends/native/meta-kms-impl-device-atomic.c index 2ca70326f..80c01413c 100644 --- a/src/backends/native/meta-kms-impl-device-atomic.c +++ b/src/backends/native/meta-kms-impl-device-atomic.c @@ -505,6 +505,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, { MetaKmsPlaneAssignment *plane_assignment = update_entry; MetaKmsPlane *plane = plane_assignment->plane; + MetaKmsUpdateFlag flags = (MetaKmsUpdateFlag) user_data; MetaDrmBuffer *buffer; MetaKmsFbDamage *fb_damage; uint32_t prop_id; @@ -657,6 +658,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, error)) return FALSE; } + + if (!(flags & META_KMS_UPDATE_FLAG_TEST_ONLY)) + meta_swap_chain_push_buffer (meta_kms_crtc_get_swap_chain (plane_assignment->crtc), + meta_kms_plane_get_id (plane), + G_OBJECT (buffer)); + return TRUE; } @@ -1005,7 +1012,7 @@ meta_kms_impl_device_atomic_process_update (MetaKmsImplDevice *impl_device, req, blob_ids, meta_kms_update_get_plane_assignments (update), - NULL, + GUINT_TO_POINTER (flags), process_plane_assignment, &error)) goto err; diff --git a/src/backends/native/meta-kms-impl-device-simple.c b/src/backends/native/meta-kms-impl-device-simple.c index 2d68ba11f..f4e23df07 100644 --- a/src/backends/native/meta-kms-impl-device-simple.c +++ b/src/backends/native/meta-kms-impl-device-simple.c @@ -485,6 +485,8 @@ process_mode_set (MetaKmsImplDevice *impl_device, return FALSE; } + meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (crtc)); + if (drm_mode) { g_hash_table_replace (impl_device_simple->cached_mode_sets, @@ -554,7 +556,7 @@ is_timestamp_earlier_than (uint64_t ts1, typedef struct _RetryPageFlipData { MetaKmsCrtc *crtc; - uint32_t fb_id; + MetaDrmBuffer *fb; MetaKmsPageFlipData *page_flip_data; float refresh_rate; uint64_t retry_time_us; @@ -567,6 +569,7 @@ retry_page_flip_data_free (RetryPageFlipData *retry_page_flip_data) g_assert (!retry_page_flip_data->page_flip_data); g_clear_pointer (&retry_page_flip_data->custom_page_flip, meta_kms_custom_page_flip_free); + g_clear_object (&retry_page_flip_data->fb); g_free (retry_page_flip_data); } @@ -634,16 +637,21 @@ retry_page_flips (gpointer user_data) } else { + uint32_t fb_id = + retry_page_flip_data->fb ? + meta_drm_buffer_get_fb_id (retry_page_flip_data->fb) : + 0; + meta_topic (META_DEBUG_KMS, "[simple] Retrying page flip on CRTC %u (%s) with %u", meta_kms_crtc_get_id (crtc), meta_kms_impl_device_get_path (impl_device), - retry_page_flip_data->fb_id); + fb_id); fd = meta_kms_impl_device_get_fd (impl_device); ret = drmModePageFlip (fd, meta_kms_crtc_get_id (crtc), - retry_page_flip_data->fb_id, + fb_id, DRM_MODE_PAGE_FLIP_EVENT, retry_page_flip_data->page_flip_data); } @@ -730,7 +738,7 @@ retry_page_flips (gpointer user_data) static void schedule_retry_page_flip (MetaKmsImplDeviceSimple *impl_device_simple, MetaKmsCrtc *crtc, - uint32_t fb_id, + MetaDrmBuffer *fb, float refresh_rate, MetaKmsPageFlipData *page_flip_data, MetaKmsCustomPageFlip *custom_page_flip) @@ -745,7 +753,7 @@ schedule_retry_page_flip (MetaKmsImplDeviceSimple *impl_device_simple, retry_page_flip_data = g_new0 (RetryPageFlipData, 1); *retry_page_flip_data = (RetryPageFlipData) { .crtc = crtc, - .fb_id = fb_id, + .fb = fb ? g_object_ref (fb) : NULL, .page_flip_data = page_flip_data, .refresh_rate = refresh_rate, .retry_time_us = retry_time_us, @@ -877,6 +885,8 @@ mode_set_fallback (MetaKmsImplDeviceSimple *impl_device_simple, return FALSE; } + meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (crtc)); + if (!impl_device_simple->mode_set_fallback_feedback_source) { MetaKmsImpl *impl = meta_kms_impl_device_get_impl (impl_device); @@ -1003,20 +1013,20 @@ dispatch_page_flip (MetaKmsImplDevice *impl_device, cached_mode_set = get_cached_mode_set (impl_device_simple, crtc); if (cached_mode_set) { - uint32_t fb_id; + MetaDrmBuffer *fb; drmModeModeInfo *drm_mode; float refresh_rate; if (plane_assignment) - fb_id = meta_drm_buffer_get_fb_id (plane_assignment->buffer); + fb = plane_assignment->buffer; else - fb_id = 0; + fb = NULL; drm_mode = cached_mode_set->drm_mode; refresh_rate = meta_calculate_drm_mode_refresh_rate (drm_mode); meta_kms_impl_device_hold_fd (impl_device); schedule_retry_page_flip (impl_device_simple, crtc, - fb_id, + fb, refresh_rate, page_flip_data, g_steal_pointer (&custom_page_flip)); @@ -1299,7 +1309,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, { case META_KMS_PLANE_TYPE_PRIMARY: /* Handled as part of the mode-set and page flip. */ - return TRUE; + goto assigned; case META_KMS_PLANE_TYPE_CURSOR: if (!process_cursor_plane_assignment (impl_device, update, plane_assignment, @@ -1313,7 +1323,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, } else { - return TRUE; + goto assigned; } case META_KMS_PLANE_TYPE_OVERLAY: error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, @@ -1326,6 +1336,12 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, } g_assert_not_reached (); + +assigned: + meta_swap_chain_push_buffer (meta_kms_crtc_get_swap_chain (plane_assignment->crtc), + meta_kms_plane_get_id (plane), + G_OBJECT (plane_assignment->buffer)); + return TRUE; } static gboolean diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c index bce64d309..85441f47b 100644 --- a/src/backends/native/meta-kms-impl-device.c +++ b/src/backends/native/meta-kms-impl-device.c @@ -1483,9 +1483,11 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, meta_kms_update_merge_from (crtc_frame->pending_update, update); meta_kms_update_free (update); update = g_steal_pointer (&crtc_frame->pending_update); - disarm_crtc_frame_deadline_timer (crtc_frame); } + if (crtc_frame->deadline.armed) + disarm_crtc_frame_deadline_timer (crtc_frame); + meta_kms_device_handle_flush (priv->device, latch_crtc); feedback = do_process (impl_device, latch_crtc, update, flags); @@ -1862,6 +1864,16 @@ meta_kms_impl_device_init_mode_setting (MetaKmsImplDevice *impl_device, return TRUE; } +static void +release_buffers (gpointer data, + gpointer user_data) +{ + MetaKmsCrtc *crtc = data; + MetaSwapChain *swap_chain = meta_kms_crtc_get_swap_chain (crtc); + + meta_swap_chain_release_buffers (swap_chain); +} + void meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device) { @@ -1869,6 +1881,8 @@ meta_kms_impl_device_prepare_shutdown (MetaKmsImplDevice *impl_device) meta_kms_impl_device_get_instance_private (impl_device); MetaKmsImplDeviceClass *klass = META_KMS_IMPL_DEVICE_GET_CLASS (impl_device); + g_list_foreach (priv->crtcs, release_buffers, NULL); + if (klass->prepare_shutdown) klass->prepare_shutdown (impl_device); diff --git a/src/backends/native/meta-kms-update.c b/src/backends/native/meta-kms-update.c index 5189c5ab3..bb7349ecf 100644 --- a/src/backends/native/meta-kms-update.c +++ b/src/backends/native/meta-kms-update.c @@ -190,6 +190,7 @@ static void meta_kms_plane_assignment_free (MetaKmsPlaneAssignment *plane_assignment) { g_clear_pointer (&plane_assignment->fb_damage, meta_kms_fb_damage_free); + g_clear_object (&plane_assignment->buffer); g_free (plane_assignment); } @@ -292,7 +293,7 @@ meta_kms_update_assign_plane (MetaKmsUpdate *update, .update = update, .crtc = crtc, .plane = plane, - .buffer = buffer, + .buffer = g_object_ref (buffer), .src_rect = src_rect, .dst_rect = dst_rect, .flags = flags, diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c index ec009ec8c..c6708946e 100644 --- a/src/backends/native/meta-kms.c +++ b/src/backends/native/meta-kms.c @@ -155,6 +155,8 @@ struct _MetaKms int kernel_thread_inhibit_count; MetaKmsCursorManager *cursor_manager; + + gboolean shutting_down; }; G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD) @@ -433,6 +435,7 @@ static void on_prepare_shutdown (MetaBackend *backend, MetaKms *kms) { + kms->shutting_down = TRUE; meta_kms_run_impl_task_sync (kms, prepare_shutdown_in_impl, NULL, NULL); meta_thread_flush_callbacks (META_THREAD (kms)); @@ -487,6 +490,12 @@ meta_kms_new (MetaBackend *backend, return kms; } +gboolean +meta_kms_is_shutting_down (MetaKms *kms) +{ + return kms->shutting_down; +} + static void meta_kms_finalize (GObject *object) { diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h index 743401406..f6b19520b 100644 --- a/src/backends/native/meta-kms.h +++ b/src/backends/native/meta-kms.h @@ -60,6 +60,8 @@ MetaKmsDevice * meta_kms_create_device (MetaKms *kms, MetaKmsDeviceFlag flags, GError **error); +gboolean meta_kms_is_shutting_down (MetaKms *kms); + MetaKms * meta_kms_new (MetaBackend *backend, MetaKmsFlags flags, GError **error); diff --git a/src/backends/native/meta-onscreen-native.c b/src/backends/native/meta-onscreen-native.c index 2388a44a2..14d727c55 100644 --- a/src/backends/native/meta-onscreen-native.c +++ b/src/backends/native/meta-onscreen-native.c @@ -72,7 +72,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState struct { MetaDrmBufferDumb *current_dumb_fb; - MetaDrmBufferDumb *dumb_fbs[2]; + MetaDrmBufferDumb *dumb_fbs[3]; } cpu; gboolean noted_primary_gpu_copy_ok; @@ -93,8 +93,13 @@ struct _MetaOnscreenNative struct { struct gbm_surface *surface; - MetaDrmBuffer *current_fb; MetaDrmBuffer *next_fb; + MetaDrmBuffer *stalled_fb; + + /* Temporary workaround for the scanout-failed signal wanting the buffer + * to live longer than it does, and then it doesn't use it anyway... + */ + MetaDrmBuffer *direct_fb; } gbm; #ifdef HAVE_EGL_DEVICE @@ -116,6 +121,16 @@ struct _MetaOnscreenNative gulong privacy_screen_changed_handler_id; gulong color_space_changed_handler_id; gulong hdr_metadata_changed_handler_id; + + gboolean needs_flush; + + unsigned int swaps_pending; + + struct { + int *rectangles; /* 4 x n_rectangles */ + int n_rectangles; + ClutterFrame *frame; + } next_post; }; G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, @@ -123,40 +138,17 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, static GQuark blit_source_quark = 0; -static gboolean -init_secondary_gpu_state (MetaRendererNative *renderer_native, - CoglOnscreen *onscreen, - GError **error); - static void -free_current_bo (CoglOnscreen *onscreen) -{ - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - - g_clear_object (&onscreen_native->gbm.current_fb); -} - -static void -meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) -{ - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - - if (!onscreen_native->gbm.next_fb) - return; - - free_current_bo (onscreen); - - g_set_object (&onscreen_native->gbm.current_fb, onscreen_native->gbm.next_fb); - g_clear_object (&onscreen_native->gbm.next_fb); -} +try_post_latest_swap (CoglOnscreen *onscreen); static void -meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen) -{ - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); +post_finish_frame (MetaOnscreenNative *onscreen_native, + MetaKmsUpdate *kms_update); - g_clear_object (&onscreen_native->gbm.next_fb); -} +static gboolean +init_secondary_gpu_state (MetaRendererNative *renderer_native, + CoglOnscreen *onscreen, + GError **error); static void maybe_update_frame_info (MetaCrtc *crtc, @@ -193,7 +185,7 @@ meta_onscreen_native_notify_frame_complete (CoglOnscreen *onscreen) info = cogl_onscreen_pop_head_frame_info (onscreen); - g_assert (!cogl_onscreen_peek_head_frame_info (onscreen)); + g_assert (info); _cogl_onscreen_notify_frame_sync (onscreen, info); _cogl_onscreen_notify_complete (onscreen, info); @@ -228,7 +220,8 @@ notify_view_crtc_presented (MetaRendererView *view, maybe_update_frame_info (crtc, frame_info, time_us, flags, sequence); meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_swap_drm_fb (onscreen); + meta_swap_chain_swap_buffers (meta_kms_crtc_get_swap_chain (kms_crtc)); + try_post_latest_swap (onscreen); } static void @@ -278,15 +271,13 @@ page_flip_feedback_ready (MetaKmsCrtc *kms_crtc, CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (CLUTTER_STAGE_VIEW (view)); CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer); - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); CoglFrameInfo *frame_info; frame_info = cogl_onscreen_peek_head_frame_info (onscreen); frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - g_warn_if_fail (!onscreen_native->gbm.next_fb); - meta_onscreen_native_notify_frame_complete (onscreen); + try_post_latest_swap (onscreen); } static void @@ -336,7 +327,7 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_clear_next_fb (onscreen); + try_post_latest_swap (onscreen); } static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { @@ -397,18 +388,40 @@ custom_egl_stream_page_flip (gpointer custom_page_flip_data, } #endif /* HAVE_EGL_DEVICE */ -void -meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) +static void +drop_stalled_swap (CoglOnscreen *onscreen) { CoglFrameInfo *frame_info; + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - meta_onscreen_native_swap_drm_fb (onscreen); + /* Remember we can't compare stalled_fb because it's not used by + * META_RENDERER_NATIVE_MODE_EGL_DEVICE. So we judge stalled to be whenever + * swaps_pending > 1. + */ + if (onscreen_native->swaps_pending <= 1) + return; + + onscreen_native->swaps_pending--; + + g_clear_object (&onscreen_native->gbm.stalled_fb); frame_info = cogl_onscreen_peek_tail_frame_info (onscreen); frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; meta_onscreen_native_notify_frame_complete (onscreen); } +void +meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen) +{ + drop_stalled_swap (onscreen); + + /* If the monitor just woke up and the shell is fully idle (has nothing + * more to swap) then we just woke to an indefinitely black screen. Let's + * fix that using the last swap (which is never classified as "stalled"). + */ + try_post_latest_swap (onscreen); +} + static void meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, MetaRendererView *view, @@ -425,7 +438,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); MetaRendererNativeGpuData *renderer_gpu_data; MetaGpuKms *gpu_kms; - MetaDrmBuffer *buffer; + g_autoptr (MetaDrmBuffer) buffer = NULL; MetaKmsPlaneAssignment *plane_assignment; COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs, @@ -440,7 +453,7 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, switch (renderer_gpu_data->mode) { case META_RENDERER_NATIVE_MODE_GBM: - buffer = onscreen_native->gbm.next_fb; + buffer = g_steal_pointer (&onscreen_native->gbm.next_fb); plane_assignment = meta_crtc_kms_assign_primary_plane (crtc_kms, buffer, @@ -596,6 +609,16 @@ import_shared_framebuffer (CoglOnscreen *onscreen, return imported_buffer; } +static void +reference_owning_gbm_surface (CoglOnscreen *onscreen, + MetaDrmBufferGbm *buffer_gbm) +{ + g_object_set_data_full (G_OBJECT (buffer_gbm), + "gbm_surface owner", + g_object_ref (onscreen), + (GDestroyNotify) g_object_unref); +} + static MetaDrmBuffer * copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, @@ -681,6 +704,8 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, return NULL; } + reference_owning_gbm_surface (onscreen, buffer_gbm); + g_object_set_qdata_full (G_OBJECT (buffer_gbm), blit_source_quark, g_object_ref (primary_gpu_fb), @@ -693,12 +718,17 @@ static MetaDrmBufferDumb * secondary_gpu_get_next_dumb_buffer (MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state) { MetaDrmBufferDumb *current_dumb_fb; + const int n_dumb_fbs = G_N_ELEMENTS (secondary_gpu_state->cpu.dumb_fbs); + int i; current_dumb_fb = secondary_gpu_state->cpu.current_dumb_fb; - if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[0]) - return secondary_gpu_state->cpu.dumb_fbs[1]; - else - return secondary_gpu_state->cpu.dumb_fbs[0]; + for (i = 0; i < n_dumb_fbs; i++) + { + if (current_dumb_fb == secondary_gpu_state->cpu.dumb_fbs[i]) + return secondary_gpu_state->cpu.dumb_fbs[(i + 1) % n_dumb_fbs]; + } + + return secondary_gpu_state->cpu.dumb_fbs[0]; } static MetaDrmBuffer * @@ -1029,10 +1059,15 @@ swap_buffer_result_feedback (const MetaKmsFeedback *kms_feedback, g_warning ("Page flip failed: %s", error->message); frame_info = cogl_onscreen_peek_head_frame_info (onscreen); - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_clear_next_fb (onscreen); + /* After resuming from suspend, drop_stalled_swap might have done this + * already and emptied the frame_info queue. + */ + if (frame_info) + { + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + meta_onscreen_native_notify_frame_complete (onscreen); + } } static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { @@ -1053,30 +1088,35 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; - MetaRenderer *renderer = META_RENDERER (renderer_native); - MetaBackend *backend = meta_renderer_get_backend (renderer); - MetaMonitorManager *monitor_manager = - meta_backend_get_monitor_manager (backend); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaGpuKms *render_gpu = onscreen_native->render_gpu; MetaDeviceFile *render_device_file; ClutterFrame *frame = user_data; - MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); - MetaKmsUpdate *kms_update; CoglOnscreenClass *parent_class; gboolean egl_context_changed = FALSE; - MetaPowerSave power_save_mode; g_autoptr (GError) error = NULL; MetaDrmBufferFlags buffer_flags; MetaDrmBufferGbm *buffer_gbm; g_autoptr (MetaDrmBuffer) primary_gpu_fb = NULL; g_autoptr (MetaDrmBuffer) secondary_gpu_fb = NULL; - MetaKmsCrtc *kms_crtc; - MetaKmsDevice *kms_device; + size_t rectangles_size; COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, "Onscreen (swap-buffers)"); + if (meta_is_topic_enabled (META_DEBUG_KMS)) + { + unsigned int frames_pending = + cogl_onscreen_count_pending_frames (onscreen); + + meta_topic (META_DEBUG_KMS, + "Swap buffers: %u frames pending (%s-buffering)", + frames_pending, + frames_pending == 1 ? "double" : + frames_pending == 2 ? "triple" : + "?"); + } + secondary_gpu_fb = update_secondary_gpu_state_pre_swap_buffers (onscreen, rectangles, @@ -1113,6 +1153,7 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, return; } + reference_owning_gbm_surface (onscreen, buffer_gbm); primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm)); break; case META_RENDERER_NATIVE_MODE_SURFACELESS: @@ -1132,7 +1173,15 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, switch (renderer_gpu_data->mode) { case META_RENDERER_NATIVE_MODE_GBM: - g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); + if (onscreen_native->gbm.next_fb != NULL) + { + g_warn_if_fail (onscreen_native->gbm.stalled_fb == NULL); + drop_stalled_swap (onscreen); + g_assert (onscreen_native->gbm.stalled_fb == NULL); + onscreen_native->gbm.stalled_fb = + g_steal_pointer (&onscreen_native->gbm.next_fb); + } + if (onscreen_native->secondary_gpu_state) g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); else @@ -1146,6 +1195,9 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, #endif } + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + /* * If we changed EGL context, cogl will have the wrong idea about what is * current, making it fail to set it when it needs to. Avoid that by making @@ -1155,12 +1207,83 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, if (egl_context_changed) _cogl_winsys_egl_ensure_current (cogl_display); - kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); - kms_device = meta_kms_crtc_get_device (kms_crtc); + rectangles_size = n_rectangles * 4 * sizeof (int); + onscreen_native->next_post.rectangles = + g_realloc (onscreen_native->next_post.rectangles, rectangles_size); + memcpy (onscreen_native->next_post.rectangles, rectangles, rectangles_size); + onscreen_native->next_post.n_rectangles = n_rectangles; + + g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); + onscreen_native->next_post.frame = clutter_frame_ref (frame); + + onscreen_native->swaps_pending++; + try_post_latest_swap (onscreen); +} + +static void +try_post_latest_swap (CoglOnscreen *onscreen) +{ + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglRenderer *cogl_renderer = cogl_context->display->renderer; + CoglRendererEGL *cogl_renderer_egl = cogl_renderer->winsys; + MetaRendererNativeGpuData *renderer_gpu_data = cogl_renderer_egl->platform; + MetaRendererNative *renderer_native = renderer_gpu_data->renderer_native; + MetaRenderer *renderer = META_RENDERER (renderer_native); + MetaBackend *backend = meta_renderer_get_backend (renderer); + MetaBackendNative *backend_native = META_BACKEND_NATIVE (backend); + MetaKms *kms = meta_backend_native_get_kms (backend_native); + MetaMonitorManager *monitor_manager = + meta_backend_get_monitor_manager (backend); + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + MetaPowerSave power_save_mode; + MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); + MetaKmsUpdate *kms_update; + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + g_autoptr (ClutterFrame) frame = NULL; + MetaFrameNative *frame_native; + + if (onscreen_native->next_post.frame == NULL || + onscreen_native->view == NULL) + return; + + if (meta_kms_is_shutting_down (kms)) + { + meta_onscreen_native_discard_pending_swaps (onscreen); + return; + } power_save_mode = meta_monitor_manager_get_power_save_mode (monitor_manager); if (power_save_mode == META_POWER_SAVE_ON) { + unsigned int frames_pending = + cogl_onscreen_count_pending_frames (onscreen); + unsigned int posts_pending; + + g_assert (frames_pending >= onscreen_native->swaps_pending); + posts_pending = frames_pending - onscreen_native->swaps_pending; + if (posts_pending > 0) + return; /* wait for the next frame notification and then try again */ + + frame = g_steal_pointer (&onscreen_native->next_post.frame); + frame_native = meta_frame_native_from_frame (frame); + + if (onscreen_native->swaps_pending == 0) + { + if (frame_native) + { + kms_update = meta_frame_native_steal_kms_update (frame_native); + if (kms_update) + post_finish_frame (onscreen_native, kms_update); + } + return; + } + + drop_stalled_swap (onscreen); + onscreen_native->swaps_pending--; + kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device); meta_kms_update_add_result_listener (kms_update, @@ -1175,15 +1298,13 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, onscreen_native->crtc, kms_update, META_KMS_PAGE_FLIP_LISTENER_FLAG_NONE, - rectangles, - n_rectangles); + onscreen_native->next_post.rectangles, + onscreen_native->next_post.n_rectangles); } else { meta_renderer_native_queue_power_save_page_flip (renderer_native, onscreen); - clutter_frame_set_result (frame, - CLUTTER_FRAME_RESULT_PENDING_PRESENTED); return; } @@ -1203,8 +1324,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, kms_update = meta_frame_native_steal_kms_update (frame_native); meta_renderer_native_queue_mode_set_update (renderer_native, kms_update); - clutter_frame_set_result (frame, - CLUTTER_FRAME_RESULT_PENDING_PRESENTED); return; } else if (meta_renderer_native_has_pending_mode_set (renderer_native)) @@ -1218,8 +1337,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, meta_frame_native_steal_kms_update (frame_native); meta_renderer_native_post_mode_set_updates (renderer_native); - clutter_frame_set_result (frame, - CLUTTER_FRAME_RESULT_PENDING_PRESENTED); return; } break; @@ -1235,8 +1352,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, kms_update); meta_renderer_native_post_mode_set_updates (renderer_native); - clutter_frame_set_result (frame, - CLUTTER_FRAME_RESULT_PENDING_PRESENTED); return; } break; @@ -1251,7 +1366,6 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, kms_update = meta_frame_native_steal_kms_update (frame_native); meta_kms_device_post_update (kms_device, kms_update, META_KMS_UPDATE_FLAG_NONE); - clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); } gboolean @@ -1296,6 +1410,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, CoglOnscreen *onscreen = COGL_ONSCREEN (onscreen_native); const GError *error; CoglFrameInfo *frame_info; + g_autoptr (MetaDrmBuffer) direct_fb = g_steal_pointer (&onscreen_native->gbm.direct_fb); error = meta_kms_feedback_get_error (kms_feedback); if (!error) @@ -1309,8 +1424,7 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, g_warning ("Direct scanout page flip failed: %s", error->message); - cogl_scanout_notify_failed (COGL_SCANOUT (onscreen_native->gbm.next_fb), - onscreen); + cogl_scanout_notify_failed (COGL_SCANOUT (direct_fb), onscreen); clutter_stage_view_add_redraw_clip (view, NULL); clutter_stage_view_schedule_update_now (view); } @@ -1319,7 +1433,6 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_clear_next_fb (onscreen); } static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { @@ -1371,6 +1484,18 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, return FALSE; } + /* Our direct scanout frame counts as 1, so more than that means we would + * be jumping the queue (and post would fail). + */ + if (cogl_onscreen_count_pending_frames (onscreen) > 1) + { + g_set_error_literal (error, + COGL_SCANOUT_ERROR, + COGL_SCANOUT_ERROR_INHIBITED, + "Direct scanout is inhibited during triple buffering"); + return FALSE; + } + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, render_gpu); @@ -1385,6 +1510,8 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, kms_device = meta_kms_crtc_get_device (kms_crtc); kms_update = meta_frame_native_ensure_kms_update (frame_native, kms_device); + g_set_object (&onscreen_native->gbm.direct_fb, + onscreen_native->gbm.next_fb); meta_kms_update_add_result_listener (kms_update, &scanout_result_listener_vtable, NULL, @@ -1430,12 +1557,6 @@ void meta_onscreen_native_before_redraw (CoglOnscreen *onscreen, ClutterFrame *frame) { - MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - MetaCrtcKms *crtc_kms = META_CRTC_KMS (onscreen_native->crtc); - MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); - - meta_kms_device_await_flush (meta_kms_crtc_get_device (kms_crtc), - kms_crtc); } void @@ -1555,22 +1676,79 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); MetaKmsUpdate *kms_update; + unsigned int frames_pending = cogl_onscreen_count_pending_frames (onscreen); + unsigned int swaps_pending = onscreen_native->swaps_pending; + unsigned int posts_pending = frames_pending - swaps_pending; - kms_update = meta_frame_native_steal_kms_update (frame_native); - if (!kms_update) + onscreen_native->needs_flush |= meta_kms_device_handle_flush (kms_device, + kms_crtc); + + if (!meta_frame_native_has_kms_update (frame_native)) { - if (meta_kms_device_handle_flush (kms_device, kms_crtc)) - { - kms_update = meta_kms_update_new (kms_device); - meta_kms_update_set_flushing (kms_update, kms_crtc); - } - else + if (!onscreen_native->needs_flush || posts_pending) { clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); return; } } + if (posts_pending && !swaps_pending) + { + g_return_if_fail (meta_frame_native_has_kms_update (frame_native)); + g_warn_if_fail (onscreen_native->next_post.frame == NULL); + + g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); + onscreen_native->next_post.frame = clutter_frame_ref (frame); + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + return; + } + + kms_update = meta_frame_native_steal_kms_update (frame_native); + + if (posts_pending && swaps_pending) + { + MetaFrameNative *older_frame_native; + MetaKmsUpdate *older_kms_update; + + g_return_if_fail (kms_update); + g_return_if_fail (onscreen_native->next_post.frame != NULL); + + older_frame_native = + meta_frame_native_from_frame (onscreen_native->next_post.frame); + older_kms_update = + meta_frame_native_ensure_kms_update (older_frame_native, kms_device); + meta_kms_update_merge_from (older_kms_update, kms_update); + meta_kms_update_free (kms_update); + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); + return; + } + + if (!kms_update) + { + kms_update = meta_kms_update_new (kms_device); + g_warn_if_fail (onscreen_native->needs_flush); + } + + if (onscreen_native->needs_flush) + { + meta_kms_update_set_flushing (kms_update, kms_crtc); + onscreen_native->needs_flush = FALSE; + } + + post_finish_frame (onscreen_native, kms_update); + + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); +} + +static void +post_finish_frame (MetaOnscreenNative *onscreen_native, + MetaKmsUpdate *kms_update) +{ + MetaCrtc *crtc = onscreen_native->crtc; + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (crtc)); + MetaKmsDevice *kms_device = meta_kms_crtc_get_device (kms_crtc); + g_autoptr (MetaKmsFeedback) kms_feedback = NULL; + meta_kms_update_add_result_listener (kms_update, &finish_frame_result_listener_vtable, NULL, @@ -1594,7 +1772,17 @@ meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, meta_kms_update_set_flushing (kms_update, kms_crtc); meta_kms_device_post_update (kms_device, kms_update, META_KMS_UPDATE_FLAG_NONE); - clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_PENDING_PRESENTED); +} + +void +meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen) +{ + MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); + + onscreen_native->swaps_pending = 0; + + g_clear_object (&onscreen_native->gbm.stalled_fb); + g_clear_object (&onscreen_native->gbm.next_fb); } static gboolean @@ -2421,7 +2609,7 @@ meta_onscreen_native_dispose (GObject *object) { case META_RENDERER_NATIVE_MODE_GBM: g_clear_object (&onscreen_native->gbm.next_fb); - free_current_bo (onscreen); + g_clear_object (&onscreen_native->gbm.direct_fb); break; case META_RENDERER_NATIVE_MODE_SURFACELESS: g_assert_not_reached (); @@ -2455,6 +2643,10 @@ meta_onscreen_native_dispose (GObject *object) g_clear_object (&onscreen_native->output); g_clear_object (&onscreen_native->crtc); + + g_clear_pointer (&onscreen_native->next_post.rectangles, g_free); + g_clear_pointer (&onscreen_native->next_post.frame, clutter_frame_unref); + onscreen_native->next_post.n_rectangles = 0; } static void diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h index 91eb7b533..11bb5ba56 100644 --- a/src/backends/native/meta-onscreen-native.h +++ b/src/backends/native/meta-onscreen-native.h @@ -45,6 +45,8 @@ void meta_onscreen_native_finish_frame (CoglOnscreen *onscreen, void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen); +void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen); + gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, MetaDrmBuffer *fb); diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c index e6c653e26..7e39889bc 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c @@ -99,6 +99,7 @@ struct _MetaRendererNative GList *detached_onscreens; GList *lingering_onscreens; + GList *disabled_crtcs; guint release_unused_gpus_idle_id; GList *power_save_page_flip_onscreens; @@ -683,6 +684,9 @@ configure_disabled_crtcs (MetaKmsDevice *kms_device, kms_update = ensure_mode_set_update (renderer_native, kms_device); meta_kms_update_mode_set (kms_update, kms_crtc, NULL, NULL); + + renderer_native->disabled_crtcs = + g_list_prepend (renderer_native->disabled_crtcs, kms_crtc); } } @@ -690,12 +694,18 @@ static gboolean dummy_power_save_page_flip_cb (gpointer user_data) { MetaRendererNative *renderer_native = user_data; + GList *old_list = + g_steal_pointer (&renderer_native->power_save_page_flip_onscreens); - g_list_foreach (renderer_native->power_save_page_flip_onscreens, + g_list_foreach (old_list, (GFunc) meta_onscreen_native_dummy_power_save_page_flip, NULL); - g_clear_list (&renderer_native->power_save_page_flip_onscreens, + g_clear_list (&old_list, g_object_unref); + + if (renderer_native->power_save_page_flip_onscreens != NULL) + return G_SOURCE_CONTINUE; + renderer_native->power_save_page_flip_source_id = 0; return G_SOURCE_REMOVE; @@ -707,6 +717,9 @@ meta_renderer_native_queue_power_save_page_flip (MetaRendererNative *renderer_na { const unsigned int timeout_ms = 100; + if (g_list_find (renderer_native->power_save_page_flip_onscreens, onscreen)) + return; + if (!renderer_native->power_save_page_flip_source_id) { renderer_native->power_save_page_flip_source_id = @@ -817,6 +830,22 @@ clear_detached_onscreens (MetaRendererNative *renderer_native) g_object_unref); } +static void +clear_disabled_crtcs (MetaRendererNative *renderer_native) +{ + GList *l; + + for (l = renderer_native->disabled_crtcs; l; l = l->next) + { + MetaKmsCrtc *kms_crtc = l->data; + MetaSwapChain *swap_chain = meta_kms_crtc_get_swap_chain (kms_crtc); + + meta_swap_chain_release_buffers (swap_chain); + } + + g_clear_list (&renderer_native->disabled_crtcs, NULL); +} + static void mode_sets_update_result_feedback (const MetaKmsFeedback *kms_feedback, gpointer user_data) @@ -878,6 +907,7 @@ meta_renderer_native_post_mode_set_updates (MetaRendererNative *renderer_native) post_mode_set_updates (renderer_native); clear_detached_onscreens (renderer_native); + clear_disabled_crtcs (renderer_native); meta_kms_notify_modes_set (kms); @@ -1467,6 +1497,26 @@ detach_onscreens (MetaRenderer *renderer) } } +static void +discard_pending_swaps (MetaRenderer *renderer) +{ + GList *views = meta_renderer_get_views (renderer);; + GList *l; + + for (l = views; l; l = l->next) + { + ClutterStageView *stage_view = l->data; + CoglFramebuffer *fb = clutter_stage_view_get_onscreen (stage_view); + CoglOnscreen *onscreen; + + if (!COGL_IS_ONSCREEN (fb)) + continue; + + onscreen = COGL_ONSCREEN (fb); + meta_onscreen_native_discard_pending_swaps (onscreen); + } +} + static void meta_renderer_native_rebuild_views (MetaRenderer *renderer) { @@ -1477,6 +1527,7 @@ meta_renderer_native_rebuild_views (MetaRenderer *renderer) MetaRendererClass *parent_renderer_class = META_RENDERER_CLASS (meta_renderer_native_parent_class); + discard_pending_swaps (renderer); meta_kms_discard_pending_page_flips (kms); g_hash_table_remove_all (renderer_native->mode_set_updates); @@ -2239,6 +2290,7 @@ meta_renderer_native_finalize (GObject *object) g_clear_handle_id (&renderer_native->release_unused_gpus_idle_id, g_source_remove); clear_detached_onscreens (renderer_native); + clear_disabled_crtcs (renderer_native); g_hash_table_destroy (renderer_native->gpu_datas); g_clear_object (&renderer_native->gles3); diff --git a/src/backends/native/meta-swap-chain.c b/src/backends/native/meta-swap-chain.c new file mode 100644 index 000000000..c3bed569d --- /dev/null +++ b/src/backends/native/meta-swap-chain.c @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Daniel van Vugt + */ + +#include "backends/native/meta-swap-chain.h" + +typedef struct +{ + GObject *front, *back; + gboolean back_is_set; +} PlaneState; + +typedef struct _MetaSwapChainPrivate MetaSwapChainPrivate; +struct _MetaSwapChainPrivate +{ + GHashTable *plane_states; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (MetaSwapChain, meta_swap_chain, G_TYPE_OBJECT) + +MetaSwapChain * +meta_swap_chain_new (void) +{ + return g_object_new (META_TYPE_SWAP_CHAIN, NULL); +} + +void +meta_swap_chain_push_buffer (MetaSwapChain *swap_chain, + unsigned int plane_id, + GObject *buffer) +{ + MetaSwapChainPrivate *priv = + meta_swap_chain_get_instance_private (swap_chain); + gpointer key = GUINT_TO_POINTER (plane_id); + PlaneState *plane_state; + + plane_state = g_hash_table_lookup (priv->plane_states, key); + if (plane_state == NULL) + { + plane_state = g_new0 (PlaneState, 1); + g_hash_table_insert (priv->plane_states, key, plane_state); + } + + plane_state->back_is_set = TRUE; /* note buffer may be NULL */ + g_set_object (&plane_state->back, buffer); +} + +static void +swap_plane_buffers (gpointer key, + gpointer value, + gpointer user_data) +{ + PlaneState *plane_state = value; + + if (plane_state->back_is_set) + { + g_set_object (&plane_state->front, plane_state->back); + g_clear_object (&plane_state->back); + plane_state->back_is_set = FALSE; + } +} + +void +meta_swap_chain_swap_buffers (MetaSwapChain *swap_chain) +{ + MetaSwapChainPrivate *priv = + meta_swap_chain_get_instance_private (swap_chain); + + g_hash_table_foreach (priv->plane_states, swap_plane_buffers, NULL); +} + +void +meta_swap_chain_release_buffers (MetaSwapChain *swap_chain) +{ + MetaSwapChainPrivate *priv = + meta_swap_chain_get_instance_private (swap_chain); + + g_hash_table_remove_all (priv->plane_states); +} + +static void +meta_swap_chain_dispose (GObject *object) +{ + MetaSwapChain *swap_chain = META_SWAP_CHAIN (object); + + meta_swap_chain_release_buffers (swap_chain); + + G_OBJECT_CLASS (meta_swap_chain_parent_class)->dispose (object); +} + +static void +meta_swap_chain_finalize (GObject *object) +{ + MetaSwapChain *swap_chain = META_SWAP_CHAIN (object); + MetaSwapChainPrivate *priv = + meta_swap_chain_get_instance_private (swap_chain); + + g_hash_table_unref (priv->plane_states); + + G_OBJECT_CLASS (meta_swap_chain_parent_class)->finalize (object); +} + +static void +destroy_plane_state (gpointer data) +{ + PlaneState *plane_state = data; + + g_clear_object (&plane_state->front); + g_clear_object (&plane_state->back); + g_free (plane_state); +} + +static void +meta_swap_chain_init (MetaSwapChain *swap_chain) +{ + MetaSwapChainPrivate *priv = + meta_swap_chain_get_instance_private (swap_chain); + + priv->plane_states = g_hash_table_new_full (NULL, + NULL, + NULL, + destroy_plane_state); +} + +static void +meta_swap_chain_class_init (MetaSwapChainClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = meta_swap_chain_dispose; + object_class->finalize = meta_swap_chain_finalize; +} diff --git a/src/backends/native/meta-swap-chain.h b/src/backends/native/meta-swap-chain.h new file mode 100644 index 000000000..bad772b89 --- /dev/null +++ b/src/backends/native/meta-swap-chain.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * Author: Daniel van Vugt + */ + +#ifndef META_SWAP_CHAIN_H +#define META_SWAP_CHAIN_H + +#include + +#define META_TYPE_SWAP_CHAIN (meta_swap_chain_get_type ()) +G_DECLARE_DERIVABLE_TYPE (MetaSwapChain, + meta_swap_chain, + META, SWAP_CHAIN, + GObject) + +struct _MetaSwapChainClass +{ + GObjectClass parent_class; +}; + +MetaSwapChain * meta_swap_chain_new (void); + +void meta_swap_chain_push_buffer (MetaSwapChain *swap_chain, + unsigned int plane_id, + GObject *buffer); + +void meta_swap_chain_swap_buffers (MetaSwapChain *swap_chain); + +void meta_swap_chain_release_buffers (MetaSwapChain *swap_chain); + +#endif /* META_SWAP_CHAIN_H */ diff --git a/src/meson.build b/src/meson.build index ca2ef166c..0038988bd 100644 --- a/src/meson.build +++ b/src/meson.build @@ -850,6 +850,8 @@ if have_native_backend 'backends/native/meta-seat-native.h', 'backends/native/meta-stage-native.c', 'backends/native/meta-stage-native.h', + 'backends/native/meta-swap-chain.c', + 'backends/native/meta-swap-chain.h', 'backends/native/meta-thread-impl.c', 'backends/native/meta-thread-impl.h', 'backends/native/meta-thread-private.h', diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c index 90ea9b581..aafa682bd 100644 --- a/src/tests/native-kms-render.c +++ b/src/tests/native-kms-render.c @@ -39,6 +39,8 @@ #include "tests/meta-wayland-test-driver.h" #include "tests/meta-wayland-test-utils.h" +#define N_FRAMES_PER_TEST 30 + typedef struct { int number_of_frames_left; @@ -46,12 +48,15 @@ typedef struct struct { int n_paints; - uint32_t fb_id; + int n_presentations; + int n_direct_scanouts; + GList *fb_ids; } scanout; gboolean wait_for_scanout; struct { + int scanouts_attempted; gboolean scanout_sabotaged; gboolean fallback_painted; guint repaint_guard_id; @@ -101,7 +106,7 @@ meta_test_kms_render_basic (void) gulong handler_id; test = (KmsRenderingTest) { - .number_of_frames_left = 10, + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), }; handler_id = g_signal_connect (stage, "after-update", @@ -123,7 +128,6 @@ on_scanout_before_update (ClutterStage *stage, KmsRenderingTest *test) { test->scanout.n_paints = 0; - test->scanout.fb_id = 0; } static void @@ -134,6 +138,7 @@ on_scanout_before_paint (ClutterStage *stage, { CoglScanout *scanout; MetaDrmBuffer *buffer; + uint32_t fb_id; scanout = clutter_stage_view_peek_scanout (stage_view); if (!scanout) @@ -141,8 +146,14 @@ on_scanout_before_paint (ClutterStage *stage, g_assert_true (META_IS_DRM_BUFFER (scanout)); buffer = META_DRM_BUFFER (scanout); - test->scanout.fb_id = meta_drm_buffer_get_fb_id (buffer); - g_assert_cmpuint (test->scanout.fb_id, >, 0); + + fb_id = meta_drm_buffer_get_fb_id (buffer); + g_assert_cmpuint (fb_id, >, 0); + test->scanout.fb_ids = g_list_append (test->scanout.fb_ids, + GUINT_TO_POINTER (fb_id)); + + /* Triple buffering, but no higher */ + g_assert_cmpuint (g_list_length (test->scanout.fb_ids), <=, 2); } static void @@ -171,12 +182,12 @@ on_scanout_presented (ClutterStage *stage, MetaDeviceFile *device_file; GError *error = NULL; drmModeCrtc *drm_crtc; + uint32_t first_fb_id_expected; - if (test->wait_for_scanout && test->scanout.n_paints > 0) + if (test->wait_for_scanout && test->scanout.fb_ids == NULL) return; - if (test->wait_for_scanout && test->scanout.fb_id == 0) - return; + test->scanout.n_presentations++; device_pool = meta_backend_native_get_device_pool (backend_native); @@ -195,15 +206,41 @@ on_scanout_presented (ClutterStage *stage, drm_crtc = drmModeGetCrtc (meta_device_file_get_fd (device_file), meta_kms_crtc_get_id (kms_crtc)); g_assert_nonnull (drm_crtc); - if (test->scanout.fb_id == 0) - g_assert_cmpuint (drm_crtc->buffer_id, !=, test->scanout.fb_id); + + if (test->scanout.fb_ids) + { + test->scanout.n_direct_scanouts++; + first_fb_id_expected = GPOINTER_TO_UINT (test->scanout.fb_ids->data); + test->scanout.fb_ids = g_list_delete_link (test->scanout.fb_ids, + test->scanout.fb_ids); + } else - g_assert_cmpuint (drm_crtc->buffer_id, ==, test->scanout.fb_id); + { + first_fb_id_expected = 0; + } + + /* The buffer ID won't match on the first frame because switching from + * triple buffered compositing to double buffered direct scanout takes + * an extra frame to drain the queue. Thereafter we are in direct scanout + * mode and expect the buffer IDs to match. + */ + if (test->scanout.n_presentations > 1) + { + if (first_fb_id_expected == 0) + g_assert_cmpuint (drm_crtc->buffer_id, !=, first_fb_id_expected); + else + g_assert_cmpuint (drm_crtc->buffer_id, ==, first_fb_id_expected); + } + drmModeFreeCrtc (drm_crtc); meta_device_file_release (device_file); - g_main_loop_quit (test->loop); + test->number_of_frames_left--; + if (test->number_of_frames_left <= 0) + g_main_loop_quit (test->loop); + else + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } typedef enum @@ -242,7 +279,9 @@ meta_test_kms_render_client_scanout (void) g_assert_nonnull (wayland_test_client); test = (KmsRenderingTest) { + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), + .scanout = {0}, .wait_for_scanout = TRUE, }; @@ -268,7 +307,8 @@ meta_test_kms_render_client_scanout (void) clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, >, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); g_debug ("Unmake fullscreen"); window = meta_find_window_from_title (test_context, "dma-buf-scanout-test"); @@ -290,10 +330,15 @@ meta_test_kms_render_client_scanout (void) g_assert_cmpint (buffer_rect.y, ==, 10); test.wait_for_scanout = FALSE; + test.number_of_frames_left = N_FRAMES_PER_TEST; + test.scanout.n_presentations = 0; + test.scanout.n_direct_scanouts = 0; + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, ==, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, 0); g_debug ("Moving back to 0, 0"); meta_window_move_frame (window, TRUE, 0, 0); @@ -305,10 +350,15 @@ meta_test_kms_render_client_scanout (void) g_assert_cmpint (buffer_rect.y, ==, 0); test.wait_for_scanout = TRUE; + test.number_of_frames_left = N_FRAMES_PER_TEST; + test.scanout.n_presentations = 0; + test.scanout.n_direct_scanouts = 0; + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); g_main_loop_run (test.loop); - g_assert_cmpuint (test.scanout.fb_id, >, 0); + g_assert_cmpint (test.scanout.n_presentations, ==, N_FRAMES_PER_TEST); + g_assert_cmpint (test.scanout.n_direct_scanouts, ==, N_FRAMES_PER_TEST); g_signal_handler_disconnect (stage, before_update_handler_id); g_signal_handler_disconnect (stage, before_paint_handler_id); @@ -362,6 +412,15 @@ on_scanout_fallback_before_paint (ClutterStage *stage, if (!scanout) return; + test->scanout_fallback.scanouts_attempted++; + + /* The first scanout candidate frame will get composited due to triple + * buffering draining the queue to drop to double buffering. So don't + * sabotage that first frame. + */ + if (test->scanout_fallback.scanouts_attempted < 2) + return; + g_assert_false (test->scanout_fallback.scanout_sabotaged); if (is_atomic_mode_setting (kms_device)) @@ -399,6 +458,15 @@ on_scanout_fallback_paint_view (ClutterStage *stage, g_clear_handle_id (&test->scanout_fallback.repaint_guard_id, g_source_remove); test->scanout_fallback.fallback_painted = TRUE; + test->scanout_fallback.scanout_sabotaged = FALSE; + } + else if (test->scanout_fallback.scanouts_attempted == 1) + { + /* Now that we've seen the first scanout attempt that was inhibited by + * triple buffering, try a second frame. The second one should scanout + * and will be sabotaged. + */ + clutter_actor_queue_redraw (CLUTTER_ACTOR (stage)); } } @@ -408,11 +476,11 @@ on_scanout_fallback_presented (ClutterStage *stage, ClutterFrameInfo *frame_info, KmsRenderingTest *test) { - if (!test->scanout_fallback.scanout_sabotaged) - return; + if (test->scanout_fallback.fallback_painted) + g_main_loop_quit (test->loop); - g_assert_true (test->scanout_fallback.fallback_painted); - g_main_loop_quit (test->loop); + test->number_of_frames_left--; + g_assert_cmpint (test->number_of_frames_left, >, 0); } static void @@ -441,6 +509,7 @@ meta_test_kms_render_client_scanout_fallback (void) g_assert_nonnull (wayland_test_client); test = (KmsRenderingTest) { + .number_of_frames_left = N_FRAMES_PER_TEST, .loop = g_main_loop_new (NULL, FALSE), };