From e1d79da6a8267f2e1598feb8d0e0889d138c77ca Mon Sep 17 00:00:00 2001 From: Mingi Sung Date: Sun, 15 Sep 2024 14:00:51 +0900 Subject: [PATCH] Merge branch 'sungmg-triple-buffering-v4-46' into gnome-46 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. Author: Daniel van Vugt Source: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441 Commit: 511a502e1a6ff6c88dcb650f1bee8f6eb894d1f6 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3184 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3265 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3799 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3817 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3829 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3830 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3891 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3934 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3958 Backported: https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4015 Signed-off-by: Mingi Sung --- clutter/clutter/clutter-frame-clock.c | 386 ++++++++++--- clutter/clutter/clutter-frame-clock.h | 15 +- clutter/clutter/clutter-frame-private.h | 1 + clutter/clutter/clutter-frame.c | 13 + clutter/clutter/clutter-frame.h | 7 + clutter/clutter/clutter-stage-view.c | 15 +- clutter/clutter/clutter-stage-view.h | 2 + cogl/cogl/cogl-onscreen-private.h | 5 +- cogl/cogl/cogl-onscreen.c | 8 + src/backends/meta-egl.c | 94 +++ src/backends/meta-egl.h | 23 + src/backends/meta-stage-impl.c | 2 + src/backends/native/meta-crtc-kms.c | 10 + src/backends/native/meta-crtc-native.c | 8 + src/backends/native/meta-crtc-native.h | 3 + src/backends/native/meta-crtc-virtual.c | 8 + src/backends/native/meta-frame-native.c | 63 ++ src/backends/native/meta-frame-native.h | 20 + src/backends/native/meta-kms-crtc.c | 70 ++- src/backends/native/meta-kms-crtc.h | 5 + src/backends/native/meta-kms-impl-device-atomic.c | 2 +- src/backends/native/meta-kms-impl-device.c | 191 ++++++- src/backends/native/meta-kms-update.c | 25 + src/backends/native/meta-kms-update.h | 9 +- src/backends/native/meta-kms.c | 9 + src/backends/native/meta-kms.h | 2 + src/backends/native/meta-onscreen-native.c | 665 +++++++++++++++------- src/backends/native/meta-onscreen-native.h | 2 + src/backends/native/meta-renderer-native.c | 34 +- src/backends/native/meta-renderer-view-native.c | 41 ++ src/core/util.c | 3 + src/meta/meta-debug.h | 2 + src/meta/util.h | 3 + src/tests/meta-monitor-manager-test.c | 6 + src/tests/native-kms-render.c | 106 +++- 35 files changed, 1530 insertions(+), 328 deletions(-) diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index 30a319f604..3418cb86c0 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -42,7 +42,16 @@ enum static guint signals[N_SIGNALS]; -#define SYNC_DELAY_FALLBACK_FRACTION 0.875 +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.875f #define MINIMUM_REFRESH_RATE 30.f @@ -70,8 +79,10 @@ typedef enum _ClutterFrameClockState CLUTTER_FRAME_CLOCK_STATE_IDLE, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED, CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW, - 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_ONE_AND_SCHEDULED_NOW, + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO, } ClutterFrameClockState; struct _ClutterFrameClock @@ -92,6 +103,7 @@ struct _ClutterFrameClock ClutterFrameClockMode mode; 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; @@ -113,6 +125,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; @@ -137,6 +152,8 @@ struct _ClutterFrameClock int64_t last_dispatch_interval_us; + int64_t deadline_evasion_us; + char *output_name; }; @@ -247,10 +264,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; @@ -277,6 +290,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, "Clutter::FrameClock::presented()"); COGL_TRACE_DESCRIBE (ClutterFrameClockNotifyPresented, @@ -366,22 +385,54 @@ 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 && - frame_info->has_valid_gpu_rendering_duration) + if ((frame_info->cpu_time_before_buffer_swap_us != 0 && + frame_info->has_valid_gpu_rendering_duration) || + 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; + + switch (frame_clock->state) + { + case CLUTTER_FRAME_CLOCK_STATE_INIT: + case CLUTTER_FRAME_CLOCK_STATE_IDLE: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + g_warn_if_reached (); + G_GNUC_FALLTHROUGH; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + 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; + } - dispatch_to_swap_us = - frame_info->cpu_time_before_buffer_swap_us - - frame_clock->last_dispatch_time_us; + if (frame_info->cpu_time_before_buffer_swap_us == 0) + { + /* User thread cursor-only updates with no "swap": we do know + * the combined time from dispatch to flip at least. + */ + dispatch_to_swap_us = 0; + swap_to_flip_us = flip_time_us - dispatch_time_us; + } + 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, @@ -389,9 +440,10 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, 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), + MAX (swap_to_rendering_done_us, swap_to_flip_us) + + frame_clock->deadline_evasion_us, frame_clock->shortterm_max_update_duration_us, - frame_clock->refresh_interval_us); + 2 * frame_clock->refresh_interval_us); maybe_update_longterm_max_duration_us (frame_clock, frame_info); @@ -400,7 +452,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); } @@ -418,11 +471,22 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock, case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: 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_ONE_AND_SCHEDULED_NOW: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + 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; } } @@ -440,26 +504,37 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: 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_ONE_AND_SCHEDULED_NOW: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + 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; } } -static int64_t -clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) +static gboolean +clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock, + int64_t *max_render_time_us) { int64_t refresh_interval_us; - int64_t max_render_time_us; refresh_interval_us = frame_clock->refresh_interval_us; 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; + return FALSE; /* 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 @@ -473,15 +548,15 @@ clutter_frame_clock_compute_max_render_time_us (ClutterFrameClock *frame_clock) * - The duration of vertical blank. * - A constant to account for variations in the above estimates. */ - max_render_time_us = + *max_render_time_us = MAX (frame_clock->longterm_max_update_duration_us, frame_clock->shortterm_max_update_duration_us) + frame_clock->vblank_duration_us + clutter_max_render_time_constant_us; - max_render_time_us = CLAMP (max_render_time_us, 0, refresh_interval_us); + *max_render_time_us = CLAMP (*max_render_time_us, 0, 2 * refresh_interval_us); - return max_render_time_us; + return TRUE; } static void @@ -496,7 +571,9 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, int64_t min_render_time_allowed_us; int64_t max_render_time_allowed_us; int64_t next_presentation_time_us; + int64_t next_smooth_presentation_time_us = 0; int64_t next_update_time_us; + gboolean max_render_time_is_known; now_us = g_get_monotonic_time (); @@ -516,10 +593,13 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, } min_render_time_allowed_us = refresh_interval_us / 2; - max_render_time_allowed_us = - clutter_frame_clock_compute_max_render_time_us (frame_clock); - if (min_render_time_allowed_us > max_render_time_allowed_us) + max_render_time_is_known = + clutter_frame_clock_compute_max_render_time_us (frame_clock, + &max_render_time_allowed_us); + + if (max_render_time_is_known && + min_render_time_allowed_us > max_render_time_allowed_us) min_render_time_allowed_us = max_render_time_allowed_us; /* @@ -540,7 +620,29 @@ 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: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + next_smooth_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: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + next_smooth_presentation_time_us = last_presentation_time_us + + 2 * refresh_interval_us; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: + g_warn_if_reached (); /* quad buffering would be a bug */ + next_smooth_presentation_time_us = last_presentation_time_us + + 3 * refresh_interval_us; + break; + } + + next_presentation_time_us = next_smooth_presentation_time_us; /* * However, the last presentation could have happened more than a frame ago. @@ -607,7 +709,7 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, } if (frame_clock->last_presentation_flags & CLUTTER_FRAME_INFO_FLAG_VSYNC && - next_presentation_time_us != last_presentation_time_us + refresh_interval_us) + next_presentation_time_us != next_smooth_presentation_time_us) { /* 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 @@ -619,6 +721,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock, } else { + /* If the max render time isn't known then using the current value of + * next_presentation_time_us is suboptimal. Targeting always one frame + * prior to that we'd lose the ability to scale up to triple buffering + * on late presentation. But targeting two frames prior we would be + * always triple buffering even when not required. + * So the algorithm for deciding when to scale up to triple buffering + * in the absence of render time measurements is to simply target full + * frame rate. If we're keeping up then we'll stay double buffering. If + * we're not keeping up then this will switch us to triple buffering. + */ + if (!max_render_time_is_known) + { + max_render_time_allowed_us = + (int64_t) (refresh_interval_us * SYNC_DELAY_FALLBACK_FRACTION); + next_presentation_time_us = + last_presentation_time_us + refresh_interval_us; + } + while (next_presentation_time_us - min_render_time_allowed_us < now_us) next_presentation_time_us += refresh_interval_us; @@ -650,7 +770,9 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, refresh_interval_us = frame_clock->refresh_interval_us; - if (frame_clock->last_presentation_time_us == 0) + if (frame_clock->last_presentation_time_us == 0 || + !clutter_frame_clock_compute_max_render_time_us (frame_clock, + &max_render_time_allowed_us)) { *out_next_update_time_us = frame_clock->last_dispatch_time_us ? @@ -663,9 +785,6 @@ calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock, return; } - max_render_time_allowed_us = - clutter_frame_clock_compute_max_render_time_us (frame_clock); - last_presentation_time_us = frame_clock->last_presentation_time_us; next_presentation_time_us = last_presentation_time_us + refresh_interval_us; @@ -739,8 +858,17 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock) frame_clock->pending_reschedule_now = 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_AND_SCHEDULED_NOW: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = 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; } @@ -759,6 +887,25 @@ clutter_frame_clock_uninhibit (ClutterFrameClock *frame_clock) maybe_reschedule_update (frame_clock); } +static gboolean +want_triple_buffering (ClutterFrameClock *frame_clock) +{ + switch (triple_buffering_mode) + { + case TRIPLE_BUFFERING_MODE_NEVER: + return FALSE; + case TRIPLE_BUFFERING_MODE_AUTO: + return frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_FIXED && + !(frame_clock->last_flip_hints & + CLUTTER_FRAME_HINT_DIRECT_SCANOUT_ATTEMPTED); + case TRIPLE_BUFFERING_MODE_ALWAYS: + return TRUE; + } + + g_assert_not_reached (); + return FALSE; +} + void clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) { @@ -776,11 +923,24 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_INIT: case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; break; case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: return; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING: - case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: + if (want_triple_buffering (frame_clock)) + { + frame_clock->state = + CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW; + break; + } + G_GNUC_FALLTHROUGH; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: frame_clock->pending_reschedule = TRUE; frame_clock->pending_reschedule_now = TRUE; return; @@ -809,13 +969,17 @@ 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_NOW; } 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 && + !want_triple_buffering (frame_clock)) + current_mode = TRIPLE_BUFFERING_MODE_NEVER; if (frame_clock->inhibit_count > 0) { @@ -831,12 +995,33 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; return; case CLUTTER_FRAME_CLOCK_STATE_IDLE: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED; break; case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: 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: + 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; + goto got_update_time; + } + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO: frame_clock->pending_reschedule = TRUE; return; } @@ -861,11 +1046,11 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) break; } +got_update_time: g_warn_if_fail (next_update_time_us != -1); 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; } void @@ -881,6 +1066,8 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, { 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: break; case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: frame_clock->pending_reschedule = TRUE; @@ -891,8 +1078,14 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock, frame_clock->pending_reschedule_now = 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_AND_SCHEDULED_NOW: + frame_clock->pending_reschedule = TRUE; + frame_clock->pending_reschedule_now = TRUE; + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; break; } @@ -928,7 +1121,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; @@ -949,10 +1142,27 @@ 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: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE; + break; + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED: + case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_TWO; + break; + } frame_count = frame_clock->frame_count++; @@ -983,26 +1193,36 @@ 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_CLOCK_STATE_SCHEDULED_NOW: + 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: + case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: + 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_ONE_AND_SCHEDULED_NOW: + frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW; + 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; } @@ -1035,21 +1255,31 @@ 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 * clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clock) { + int64_t max_render_time_us; int64_t max_update_duration_us; GString *string; - string = g_string_new (NULL); - g_string_append_printf (string, "Max render time: %ld µs", - clutter_frame_clock_compute_max_render_time_us (frame_clock)); + string = g_string_new ("Max render time: "); + if (!clutter_frame_clock_compute_max_render_time_us (frame_clock, + &max_render_time_us)) + { + g_string_append (string, "unknown"); + return string; + } + + g_string_append_printf (string, "%ld µs", max_render_time_us); if (frame_clock->got_measurements_last_frame) g_string_append_printf (string, " ="); @@ -1216,8 +1446,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); @@ -1241,6 +1469,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; @@ -1253,3 +1490,10 @@ clutter_frame_clock_class_init (ClutterFrameClockClass *klass) G_TYPE_NONE, 0); } + +void +clutter_frame_clock_set_deadline_evasion (ClutterFrameClock *frame_clock, + int64_t deadline_evasion_us) +{ + frame_clock->deadline_evasion_us = deadline_evasion_us; +} diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h index a7be5ef316..23e3357d3a 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, @@ -102,7 +108,12 @@ 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); + +CLUTTER_EXPORT +void clutter_frame_clock_set_deadline_evasion (ClutterFrameClock *frame_clock, + int64_t deadline_evasion_us); diff --git a/clutter/clutter/clutter-frame-private.h b/clutter/clutter/clutter-frame-private.h index ef66b874ed..ce140560a8 100644 --- a/clutter/clutter/clutter-frame-private.h +++ b/clutter/clutter/clutter-frame-private.h @@ -36,6 +36,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 7436f9f182..53c289b2c5 100644 --- a/clutter/clutter/clutter-frame.c +++ b/clutter/clutter/clutter-frame.c @@ -115,3 +115,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 34f0770bd7..c7b3d02acb 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 f5188e2acf..a89a520efb 100644 --- a/clutter/clutter/clutter-stage-view.c +++ b/clutter/clutter/clutter-stage-view.c @@ -740,6 +740,10 @@ clutter_stage_view_schedule_update (ClutterStageView *view) { ClutterStageViewPrivate *priv = clutter_stage_view_get_instance_private (view); + ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view); + + if (view_class->schedule_update) + view_class->schedule_update (view); clutter_frame_clock_schedule_update (priv->frame_clock); } @@ -898,14 +902,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/clutter/clutter/clutter-stage-view.h b/clutter/clutter/clutter-stage-view.h index c0888f7f94..bdaa065c00 100644 --- a/clutter/clutter/clutter-stage-view.h +++ b/clutter/clutter/clutter-stage-view.h @@ -54,6 +54,8 @@ struct _ClutterStageViewClass ClutterFrame * (* new_frame) (ClutterStageView *view); ClutterPaintFlag (* get_default_paint_flags) (ClutterStageView *view); + + void (* schedule_update) (ClutterStageView *view); }; CLUTTER_EXPORT diff --git a/cogl/cogl/cogl-onscreen-private.h b/cogl/cogl/cogl-onscreen-private.h index 959a60533b..cfd45a6250 100644 --- a/cogl/cogl/cogl-onscreen-private.h +++ b/cogl/cogl/cogl-onscreen-private.h @@ -78,4 +78,7 @@ COGL_EXPORT CoglFrameInfo * cogl_onscreen_peek_tail_frame_info (CoglOnscreen *onscreen); COGL_EXPORT CoglFrameInfo * -cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); +cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen); + +COGL_EXPORT unsigned int +cogl_onscreen_get_pending_frame_count (CoglOnscreen *onscreen); diff --git a/cogl/cogl/cogl-onscreen.c b/cogl/cogl/cogl-onscreen.c index e2b3a0b462..53ffa649bc 100644 --- a/cogl/cogl/cogl-onscreen.c +++ b/cogl/cogl/cogl-onscreen.c @@ -514,6 +514,14 @@ cogl_onscreen_pop_head_frame_info (CoglOnscreen *onscreen) return g_queue_pop_head (&priv->pending_frame_infos); } +unsigned int +cogl_onscreen_get_pending_frame_count (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-egl.c b/src/backends/meta-egl.c index cc023f705c..bc188e80e6 100644 --- a/src/backends/meta-egl.c +++ b/src/backends/meta-egl.c @@ -44,6 +44,11 @@ struct _MetaEgl PFNEGLCREATEIMAGEKHRPROC eglCreateImageKHR; PFNEGLDESTROYIMAGEKHRPROC eglDestroyImageKHR; + PFNEGLCREATESYNCPROC eglCreateSync; + PFNEGLDESTROYSYNCPROC eglDestroySync; + PFNEGLWAITSYNCPROC eglWaitSync; + PFNEGLDUPNATIVEFENCEFDANDROIDPROC eglDupNativeFenceFDANDROID; + PFNEGLBINDWAYLANDDISPLAYWL eglBindWaylandDisplayWL; PFNEGLQUERYWAYLANDBUFFERWL eglQueryWaylandBufferWL; @@ -1162,6 +1167,90 @@ meta_egl_query_display_attrib (MetaEgl *egl, return TRUE; } +gboolean +meta_egl_create_sync (MetaEgl *egl, + EGLDisplay display, + EGLenum type, + const EGLAttrib *attrib_list, + EGLSync *egl_sync, + GError **error) +{ + if (!is_egl_proc_valid (egl->eglCreateSync, error)) + return FALSE; + + EGLSync sync; + + sync = egl->eglCreateSync (display, type, attrib_list); + + if (sync == EGL_NO_SYNC) + { + set_egl_error (error); + return FALSE; + } + + *egl_sync = sync; + + return TRUE; +} + +gboolean +meta_egl_destroy_sync (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + GError **error) +{ + if (!is_egl_proc_valid (egl->eglDestroySync, error)) + return FALSE; + + if (!egl->eglDestroySync (display, sync)) + { + set_egl_error (error); + return FALSE; + } + + return TRUE; +} + +gboolean +meta_egl_wait_sync (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + EGLint flags, + GError **error) +{ + if (!is_egl_proc_valid (egl->eglWaitSync, error)) + return FALSE; + + if (!egl->eglWaitSync (display, sync, flags)) + { + set_egl_error (error); + return FALSE; + } + + return TRUE; +} + +EGLint +meta_egl_duplicate_native_fence_fd (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + GError **error) +{ + if (!is_egl_proc_valid (egl->eglDupNativeFenceFDANDROID, error)) + return EGL_NO_NATIVE_FENCE_FD_ANDROID; + + EGLint fd = EGL_NO_NATIVE_FENCE_FD_ANDROID; + + fd = egl->eglDupNativeFenceFDANDROID (display, sync); + + if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) + { + set_egl_error (error); + } + + return fd; +} + #define GET_EGL_PROC_ADDR(proc) \ egl->proc = (void *) eglGetProcAddress (#proc); @@ -1175,6 +1264,11 @@ meta_egl_constructed (GObject *object) GET_EGL_PROC_ADDR (eglCreateImageKHR); GET_EGL_PROC_ADDR (eglDestroyImageKHR); + GET_EGL_PROC_ADDR (eglCreateSync); + GET_EGL_PROC_ADDR (eglDestroySync); + GET_EGL_PROC_ADDR (eglWaitSync); + GET_EGL_PROC_ADDR (eglDupNativeFenceFDANDROID); + GET_EGL_PROC_ADDR (eglBindWaylandDisplayWL); GET_EGL_PROC_ADDR (eglQueryWaylandBufferWL); diff --git a/src/backends/meta-egl.h b/src/backends/meta-egl.h index 8b955c90c3..9078b47948 100644 --- a/src/backends/meta-egl.h +++ b/src/backends/meta-egl.h @@ -276,3 +276,26 @@ gboolean meta_egl_query_display_attrib (MetaEgl *egl, EGLint attribute, EGLAttrib *value, GError **error); + +gboolean meta_egl_create_sync (MetaEgl *egl, + EGLDisplay display, + EGLenum type, + const EGLAttrib *attrib_list, + EGLSync *egl_sync, + GError **error); + +gboolean meta_egl_destroy_sync (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + GError **error); + +gboolean meta_egl_wait_sync (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + EGLint flags, + GError **error); + +EGLint meta_egl_duplicate_native_fence_fd (MetaEgl *egl, + EGLDisplay display, + EGLSync sync, + GError **error); diff --git a/src/backends/meta-stage-impl.c b/src/backends/meta-stage-impl.c index 7aa24439d7..727e1a5f30 100644 --- a/src/backends/meta-stage-impl.c +++ b/src/backends/meta-stage-impl.c @@ -774,6 +774,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 3498f922a3..8cb0e935a7 100644 --- a/src/backends/native/meta-crtc-kms.c +++ b/src/backends/native/meta-crtc-kms.c @@ -348,6 +348,15 @@ meta_crtc_kms_is_hw_cursor_supported (MetaCrtcNative *crtc_native) return meta_kms_device_has_cursor_plane_for (kms_device, kms_crtc); } +static int64_t +meta_crtc_kms_get_deadline_evasion (MetaCrtcNative *crtc_native) +{ + MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc_native); + MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); + + return meta_kms_crtc_get_deadline_evasion (kms_crtc); +} + MetaKmsPlane * meta_crtc_kms_get_assigned_cursor_plane (MetaCrtcKms *crtc_kms) { @@ -479,6 +488,7 @@ meta_crtc_kms_class_init (MetaCrtcKmsClass *klass) crtc_native_class->is_transform_handled = meta_crtc_kms_is_transform_handled; crtc_native_class->is_hw_cursor_supported = meta_crtc_kms_is_hw_cursor_supported; + crtc_native_class->get_deadline_evasion = meta_crtc_kms_get_deadline_evasion; signals[GAMMA_LUT_CHANGED] = g_signal_new ("gamma-lut-changed", diff --git a/src/backends/native/meta-crtc-native.c b/src/backends/native/meta-crtc-native.c index fc280f696e..80406a5033 100644 --- a/src/backends/native/meta-crtc-native.c +++ b/src/backends/native/meta-crtc-native.c @@ -39,6 +39,14 @@ meta_crtc_native_is_hw_cursor_supported (MetaCrtcNative *crtc_native) return klass->is_hw_cursor_supported (crtc_native); } +int64_t +meta_crtc_native_get_deadline_evasion (MetaCrtcNative *crtc_native) +{ + MetaCrtcNativeClass *klass = META_CRTC_NATIVE_GET_CLASS (crtc_native); + + return klass->get_deadline_evasion (crtc_native); +} + static void meta_crtc_native_init (MetaCrtcNative *crtc_native) { diff --git a/src/backends/native/meta-crtc-native.h b/src/backends/native/meta-crtc-native.h index 3fb4bc7586..1f9a2ed46c 100644 --- a/src/backends/native/meta-crtc-native.h +++ b/src/backends/native/meta-crtc-native.h @@ -31,9 +31,12 @@ struct _MetaCrtcNativeClass gboolean (* is_transform_handled) (MetaCrtcNative *crtc_native, MetaMonitorTransform monitor_transform); gboolean (* is_hw_cursor_supported) (MetaCrtcNative *crtc_native); + int64_t (* get_deadline_evasion) (MetaCrtcNative *crtc_native); }; gboolean meta_crtc_native_is_transform_handled (MetaCrtcNative *crtc_native, MetaMonitorTransform transform); gboolean meta_crtc_native_is_hw_cursor_supported (MetaCrtcNative *crtc_native); + +int64_t meta_crtc_native_get_deadline_evasion (MetaCrtcNative *crtc_native); diff --git a/src/backends/native/meta-crtc-virtual.c b/src/backends/native/meta-crtc-virtual.c index 76165eb1bd..00b599c612 100644 --- a/src/backends/native/meta-crtc-virtual.c +++ b/src/backends/native/meta-crtc-virtual.c @@ -70,6 +70,12 @@ meta_crtc_virtual_is_hw_cursor_supported (MetaCrtcNative *crtc_native) return TRUE; } +static int64_t +meta_crtc_virtual_get_deadline_evasion (MetaCrtcNative *crtc_native) +{ + return 0; +} + static void meta_crtc_virtual_init (MetaCrtcVirtual *crtc_virtual) { @@ -89,4 +95,6 @@ meta_crtc_virtual_class_init (MetaCrtcVirtualClass *klass) meta_crtc_virtual_is_transform_handled; crtc_native_class->is_hw_cursor_supported = meta_crtc_virtual_is_hw_cursor_supported; + crtc_native_class->get_deadline_evasion = + meta_crtc_virtual_get_deadline_evasion; } diff --git a/src/backends/native/meta-frame-native.c b/src/backends/native/meta-frame-native.c index 146144db0e..8dc9dba9b9 100644 --- a/src/backends/native/meta-frame-native.c +++ b/src/backends/native/meta-frame-native.c @@ -27,7 +27,15 @@ struct _MetaFrameNative { ClutterFrame base; + MetaDrmBuffer *buffer; + CoglScanout *scanout; + MetaKmsUpdate *kms_update; + + struct { + int n_rectangles; + int *rectangles; /* 4 x n_rectangles */ + } damage; }; static void @@ -35,6 +43,10 @@ meta_frame_native_release (ClutterFrame *frame) { MetaFrameNative *frame_native = meta_frame_native_from_frame (frame); + g_clear_pointer (&frame_native->damage.rectangles, g_free); + g_clear_object (&frame_native->buffer); + g_clear_object (&frame_native->scanout); + g_return_if_fail (!frame_native->kms_update); } @@ -76,3 +88,54 @@ meta_frame_native_has_kms_update (MetaFrameNative *frame_native) { return !!frame_native->kms_update; } + +void +meta_frame_native_set_buffer (MetaFrameNative *frame_native, + MetaDrmBuffer *buffer) +{ + g_set_object (&frame_native->buffer, buffer); +} + +MetaDrmBuffer * +meta_frame_native_get_buffer (MetaFrameNative *frame_native) +{ + return frame_native->buffer; +} + +void +meta_frame_native_set_scanout (MetaFrameNative *frame_native, + CoglScanout *scanout) +{ + g_set_object (&frame_native->scanout, scanout); +} + +CoglScanout * +meta_frame_native_get_scanout (MetaFrameNative *frame_native) +{ + return frame_native->scanout; +} + +void +meta_frame_native_set_damage (MetaFrameNative *frame_native, + const int *rectangles, + int n_rectangles) +{ + size_t rectangles_size; + + rectangles_size = n_rectangles * 4 * sizeof (int); + + frame_native->damage.rectangles = + g_realloc (frame_native->damage.rectangles, rectangles_size); + memcpy (frame_native->damage.rectangles, rectangles, rectangles_size); + frame_native->damage.n_rectangles = n_rectangles; +} + +int +meta_frame_native_get_damage (MetaFrameNative *frame_native, + int **rectangles) +{ + if (rectangles) + *rectangles = frame_native->damage.rectangles; + + return frame_native->damage.n_rectangles; +} diff --git a/src/backends/native/meta-frame-native.h b/src/backends/native/meta-frame-native.h index f86d3b0b02..84bd43b8c6 100644 --- a/src/backends/native/meta-frame-native.h +++ b/src/backends/native/meta-frame-native.h @@ -17,6 +17,7 @@ #pragma once +#include "backends/native/meta-backend-native-types.h" #include "backends/native/meta-kms-types.h" #include "clutter/clutter.h" #include "core/util-private.h" @@ -36,3 +37,22 @@ MetaKmsUpdate * meta_frame_native_steal_kms_update (MetaFrameNative *frame_nativ META_EXPORT_TEST gboolean meta_frame_native_has_kms_update (MetaFrameNative *frame_native); + +void meta_frame_native_set_buffer (MetaFrameNative *frame_native, + MetaDrmBuffer *buffer); + +MetaDrmBuffer * meta_frame_native_get_buffer (MetaFrameNative *frame_native); + +void meta_frame_native_set_scanout (MetaFrameNative *frame_native, + CoglScanout *scanout); + +CoglScanout * meta_frame_native_get_scanout (MetaFrameNative *frame_native); + +void +meta_frame_native_set_damage (MetaFrameNative *frame_native, + const int *rectangles, + int n_rectangles); + +int +meta_frame_native_get_damage (MetaFrameNative *frame_native, + int **rectangles); diff --git a/src/backends/native/meta-kms-crtc.c b/src/backends/native/meta-kms-crtc.c index a0872089a0..50edfcbe33 100644 --- a/src/backends/native/meta-kms-crtc.c +++ b/src/backends/native/meta-kms-crtc.c @@ -28,8 +28,7 @@ #include "backends/native/meta-kms-update-private.h" #include "backends/native/meta-kms-utils.h" -#define DEADLINE_EVASION_US 800 -#define DEADLINE_EVASION_WITH_KMS_TOPIC_US 1000 +#define DEADLINE_EVASION_CONSTANT_US 200 #define MINIMUM_REFRESH_RATE 30.f @@ -50,6 +49,10 @@ struct _MetaKmsCrtc MetaKmsCrtcState current_state; MetaKmsCrtcPropTable prop_table; + + int64_t shortterm_max_dispatch_duration_us; + int64_t deadline_evasion_us; + int64_t deadline_evasion_update_time_us; }; G_DEFINE_TYPE (MetaKmsCrtc, meta_kms_crtc, G_TYPE_OBJECT) @@ -543,6 +546,33 @@ get_crtc_type_bitmask (MetaKmsCrtc *crtc) } } +static void +maybe_update_deadline_evasion (MetaKmsCrtc *crtc, + int64_t next_presentation_time_us) +{ + /* Do not update long-term max if there has been no measurement */ + if (!crtc->shortterm_max_dispatch_duration_us) + return; + + if (next_presentation_time_us - crtc->deadline_evasion_update_time_us < + G_USEC_PER_SEC) + return; + + if (crtc->deadline_evasion_us > crtc->shortterm_max_dispatch_duration_us) + { + /* Exponential drop-off toward the clamped short-term max */ + crtc->deadline_evasion_us -= + (crtc->deadline_evasion_us - crtc->shortterm_max_dispatch_duration_us) / 2; + } + else + { + crtc->deadline_evasion_us = crtc->shortterm_max_dispatch_duration_us; + } + + crtc->shortterm_max_dispatch_duration_us = 0; + crtc->deadline_evasion_update_time_us = next_presentation_time_us; +} + gboolean meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc, int64_t *out_next_deadline_us, @@ -610,10 +640,8 @@ meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc, * */ - if (meta_is_topic_enabled (META_DEBUG_KMS)) - deadline_evasion_us = DEADLINE_EVASION_WITH_KMS_TOPIC_US; - else - deadline_evasion_us = DEADLINE_EVASION_US; + deadline_evasion_us = meta_kms_crtc_get_deadline_evasion (crtc); + maybe_update_deadline_evasion (crtc, next_presentation_us); vblank_duration_us = meta_calculate_drm_mode_vblank_duration_us (drm_mode); next_deadline_us = next_presentation_us - (vblank_duration_us + @@ -625,3 +653,33 @@ meta_kms_crtc_determine_deadline (MetaKmsCrtc *crtc, return TRUE; } + +void +meta_kms_crtc_update_shortterm_max_dispatch_duration (MetaKmsCrtc *crtc, + int64_t duration_us) +{ + int64_t refresh_interval_us; + + if (duration_us <= crtc->shortterm_max_dispatch_duration_us) + return; + + refresh_interval_us = + (int64_t) (0.5 + G_USEC_PER_SEC / + meta_calculate_drm_mode_refresh_rate (&crtc->current_state.drm_mode)); + + crtc->shortterm_max_dispatch_duration_us = MIN (duration_us, refresh_interval_us); +} + +int64_t +meta_kms_crtc_get_deadline_evasion (MetaKmsCrtc *crtc) +{ + int64_t deadline_evasion_us; + + deadline_evasion_us = + MAX (crtc->shortterm_max_dispatch_duration_us, crtc->deadline_evasion_us); + + if (!deadline_evasion_us) + return 0; + + return deadline_evasion_us + DEADLINE_EVASION_CONSTANT_US; +} diff --git a/src/backends/native/meta-kms-crtc.h b/src/backends/native/meta-kms-crtc.h index 580ee9a89c..b293f8e7cc 100644 --- a/src/backends/native/meta-kms-crtc.h +++ b/src/backends/native/meta-kms-crtc.h @@ -65,3 +65,8 @@ int meta_kms_crtc_get_idx (MetaKmsCrtc *crtc); META_EXPORT_TEST gboolean meta_kms_crtc_is_active (MetaKmsCrtc *crtc); + +void meta_kms_crtc_update_shortterm_max_dispatch_duration (MetaKmsCrtc *crtc, + int64_t duration_us); + +int64_t meta_kms_crtc_get_deadline_evasion (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 7812ec0421..488d24ac5d 100644 --- a/src/backends/native/meta-kms-impl-device-atomic.c +++ b/src/backends/native/meta-kms-impl-device-atomic.c @@ -643,7 +643,7 @@ process_plane_assignment (MetaKmsImplDevice *impl_device, return FALSE; } - if (plane_assignment->flags & META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT) + if (plane_assignment->flags & META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC) { int signaled_sync_file; diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c index e48cffc4f1..a0de4158ac 100644 --- a/src/backends/native/meta-kms-impl-device.c +++ b/src/backends/native/meta-kms-impl-device.c @@ -39,6 +39,7 @@ #include "backends/native/meta-kms-plane-private.h" #include "backends/native/meta-kms-plane.h" #include "backends/native/meta-kms-private.h" +#include "backends/native/meta-kms-utils.h" #include "backends/native/meta-thread-private.h" #include "meta-default-modes.h" @@ -71,9 +72,17 @@ typedef struct _CrtcDeadline GSource *source; gboolean armed; gboolean is_deadline_page_flip; + int64_t expected_deadline_time_us; int64_t expected_presentation_time_us; gboolean has_expected_presentation_time; } deadline; + + struct { + MetaKmsUpdate *kms_update; + MetaKmsUpdateFlag flags; + MetaKmsCrtc *latch_crtc; + GSource *source; + } submitted_update; } CrtcFrame; typedef enum _MetaDeadlineTimerState @@ -1167,6 +1176,7 @@ arm_crtc_frame_deadline_timer (CrtcFrame *crtc_frame, timerfd_settime (crtc_frame->deadline.timer_fd, TFD_TIMER_ABSTIME, &its, NULL); + crtc_frame->deadline.expected_deadline_time_us = next_deadline_us; crtc_frame->deadline.expected_presentation_time_us = next_presentation_us; crtc_frame->deadline.has_expected_presentation_time = next_presentation_us != 0; crtc_frame->deadline.armed = TRUE; @@ -1199,7 +1209,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc, CrtcFrame *crtc_frame = user_data; if (crtc_frame->deadline.is_deadline_page_flip && - meta_is_topic_enabled (META_DEBUG_KMS)) + meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE)) { struct timeval page_flip_timeval; int64_t presentation_time_us; @@ -1212,7 +1222,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc, if (crtc_frame->deadline.has_expected_presentation_time) { - meta_topic (META_DEBUG_KMS, + meta_topic (META_DEBUG_KMS_DEADLINE, "Deadline page flip presentation time: %" G_GINT64_FORMAT " us, " "expected %" G_GINT64_FORMAT " us " "(diff: %" G_GINT64_FORMAT ")", @@ -1223,7 +1233,7 @@ crtc_page_flip_feedback_flipped (MetaKmsCrtc *crtc, } else { - meta_topic (META_DEBUG_KMS, + meta_topic (META_DEBUG_KMS_DEADLINE, "Deadline page flip presentation time: %" G_GINT64_FORMAT " us", presentation_time_us); } @@ -1392,11 +1402,16 @@ crtc_frame_deadline_dispatch (MetaThreadImpl *thread_impl, GError **error) { CrtcFrame *crtc_frame = user_data; - MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_frame->crtc); + MetaKmsCrtc *crtc = crtc_frame->crtc; + MetaKmsDevice *device = meta_kms_crtc_get_device (crtc); MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); g_autoptr (MetaKmsFeedback) feedback = NULL; uint64_t timer_value; ssize_t ret; + int64_t dispatch_time_us = 0, update_done_time_us, interval_us; + + if (meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE)) + dispatch_time_us = g_get_monotonic_time (); ret = read (crtc_frame->deadline.timer_fd, &timer_value, @@ -1418,6 +1433,36 @@ crtc_frame_deadline_dispatch (MetaThreadImpl *thread_impl, crtc_frame->crtc, g_steal_pointer (&crtc_frame->pending_update), META_KMS_UPDATE_FLAG_NONE); + + update_done_time_us = g_get_monotonic_time (); + /* Calculate how long after the planned start of deadline dispatch it finished */ + interval_us = update_done_time_us - crtc_frame->deadline.expected_deadline_time_us; + + if (meta_is_topic_enabled (META_DEBUG_KMS_DEADLINE)) + { + int64_t deadline_evasion_us, lateness_us, duration_us, vblank_delta_us; + + deadline_evasion_us = meta_kms_crtc_get_deadline_evasion (crtc); + lateness_us = dispatch_time_us - + crtc_frame->deadline.expected_deadline_time_us; + duration_us = update_done_time_us - dispatch_time_us; + vblank_delta_us = deadline_evasion_us - lateness_us - duration_us; + + meta_topic (META_DEBUG_KMS_DEADLINE, + "Deadline evasion %3"G_GINT64_FORMAT "µs, " + "dispatch started %3"G_GINT64_FORMAT "µs %s and " + "completed %3"G_GINT64_FORMAT "µs after that, " + "%3"G_GINT64_FORMAT "µs %s start of vblank.", + deadline_evasion_us, + ABS (lateness_us), + lateness_us >= 0 ? "late" : "early", + duration_us, + ABS (vblank_delta_us), + vblank_delta_us >= 0 ? "before" : "after"); + } + + meta_kms_crtc_update_shortterm_max_dispatch_duration (crtc, interval_us); + if (meta_kms_feedback_did_pass (feedback)) crtc_frame->deadline.is_deadline_page_flip = TRUE; disarm_crtc_frame_deadline_timer (crtc_frame); @@ -1431,6 +1476,8 @@ crtc_frame_free (CrtcFrame *crtc_frame) g_clear_fd (&crtc_frame->deadline.timer_fd, NULL); g_clear_pointer (&crtc_frame->deadline.source, g_source_destroy); g_clear_pointer (&crtc_frame->pending_update, meta_kms_update_free); + g_clear_pointer (&crtc_frame->submitted_update.kms_update, meta_kms_update_free); + g_clear_pointer (&crtc_frame->submitted_update.source, g_source_destroy); g_free (crtc_frame); } @@ -1539,6 +1586,79 @@ queue_update (MetaKmsImplDevice *impl_device, } } +static gpointer +meta_kms_impl_device_update_ready (MetaThreadImpl *impl, + gpointer user_data, + GError **error) +{ + CrtcFrame *crtc_frame = user_data; + MetaKmsDevice *device = meta_kms_crtc_get_device (crtc_frame->crtc); + MetaKmsImplDevice *impl_device = meta_kms_device_get_impl_device (device); + MetaKmsImplDevicePrivate *priv = + meta_kms_impl_device_get_instance_private (impl_device); + MetaKmsUpdate *update; + MetaKmsCrtc *latch_crtc; + MetaKmsFeedback *feedback; + + meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl)); + + g_clear_pointer (&crtc_frame->submitted_update.source, g_source_destroy); + + update = g_steal_pointer (&crtc_frame->submitted_update.kms_update); + meta_kms_update_realize (update, impl_device); + + latch_crtc = g_steal_pointer (&crtc_frame->submitted_update.latch_crtc); + + if (crtc_frame->pending_page_flip && + !meta_kms_update_get_mode_sets (update)) + { + g_assert (latch_crtc); + + meta_topic (META_DEBUG_KMS, + "Queuing update on CRTC %u (%s): pending page flip", + meta_kms_crtc_get_id (latch_crtc), + priv->path); + + queue_update (impl_device, crtc_frame, update); + return GINT_TO_POINTER (TRUE); + } + + if (crtc_frame->pending_update) + { + 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); + } + + meta_kms_device_handle_flush (priv->device, latch_crtc); + + feedback = do_process (impl_device, latch_crtc, update, crtc_frame->submitted_update.flags); + + if (meta_kms_feedback_did_pass (feedback) && + crtc_frame->deadline.armed) + disarm_crtc_frame_deadline_timer (crtc_frame); + + meta_kms_feedback_unref (feedback); + + return GINT_TO_POINTER (TRUE); +} + +static gboolean +is_fd_readable (int fd) +{ + GPollFD poll_fd; + + poll_fd.fd = fd; + poll_fd.events = G_IO_IN; + poll_fd.revents = 0; + + if (!g_poll (&poll_fd, 1, 0)) + return FALSE; + + return (poll_fd.revents & (G_IO_IN | G_IO_NVAL)) != 0; +} + void meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, MetaKmsUpdate *update, @@ -1546,10 +1666,15 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, { MetaKmsImplDevicePrivate *priv = meta_kms_impl_device_get_instance_private (impl_device); + MetaKmsImpl *kms_impl = meta_kms_impl_device_get_impl (impl_device); + MetaThreadImpl *thread_impl = META_THREAD_IMPL (kms_impl); g_autoptr (GError) error = NULL; MetaKmsCrtc *latch_crtc; CrtcFrame *crtc_frame; MetaKmsFeedback *feedback; + g_autoptr (GSource) source = NULL; + g_autofree char *name = NULL; + int sync_fd = -1; meta_assert_in_kms_impl (meta_kms_impl_get_kms (priv->impl)); @@ -1570,38 +1695,58 @@ meta_kms_impl_device_handle_update (MetaKmsImplDevice *impl_device, if (!ensure_device_file (impl_device, &error)) goto err; - meta_kms_update_realize (update, impl_device); - crtc_frame = ensure_crtc_frame (impl_device, latch_crtc); + if (crtc_frame->submitted_update.kms_update) + { + g_set_error (&error, G_IO_ERROR, G_IO_ERROR_PENDING, + "Previously-submitted update wasn't ready yet"); + goto err; + } + crtc_frame->await_flush = FALSE; + crtc_frame->submitted_update.kms_update = update; + crtc_frame->submitted_update.flags = flags; + crtc_frame->submitted_update.latch_crtc = latch_crtc; - if (crtc_frame->pending_page_flip && - !meta_kms_update_get_mode_sets (update)) + if (is_using_deadline_timer (impl_device)) + sync_fd = meta_kms_update_get_sync_fd (update); + + if (sync_fd >= 0) { - g_assert (latch_crtc); + GList *l; - meta_topic (META_DEBUG_KMS, - "Queuing update on CRTC %u (%s): pending page flip", - meta_kms_crtc_get_id (latch_crtc), - priv->path); + for (l = meta_kms_update_get_plane_assignments (update); l; l = l->next) + { + MetaKmsPlaneAssignment *assignment = l->data; - queue_update (impl_device, crtc_frame, update); - return; + assignment->flags |= META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC; + } } - if (crtc_frame->pending_update) + if (sync_fd < 0 || + is_fd_readable (sync_fd)) { - 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); + meta_kms_impl_device_update_ready (thread_impl, + crtc_frame, + NULL); + return; } - meta_kms_device_handle_flush (priv->device, latch_crtc); + source = meta_thread_impl_register_fd (thread_impl, + sync_fd, + meta_kms_impl_device_update_ready, + crtc_frame); - feedback = do_process (impl_device, latch_crtc, update, flags); - meta_kms_feedback_unref (feedback); + name = g_strdup_printf ("[mutter] KMS update sync_fd (crtc: %u, %s)", + meta_kms_crtc_get_id (latch_crtc), + priv->path); + g_source_set_name (source, name); + g_source_set_priority (source, G_PRIORITY_HIGH + 1); + g_source_set_can_recurse (source, FALSE); + g_source_set_ready_time (source, -1); + + crtc_frame->submitted_update.source = source; return; err: diff --git a/src/backends/native/meta-kms-update.c b/src/backends/native/meta-kms-update.c index fc605bec18..a5da81f0c5 100644 --- a/src/backends/native/meta-kms-update.c +++ b/src/backends/native/meta-kms-update.c @@ -20,6 +20,8 @@ #include "backends/native/meta-kms-update.h" #include "backends/native/meta-kms-update-private.h" +#include + #include "backends/meta-display-config-shared.h" #include "backends/native/meta-kms-connector.h" #include "backends/native/meta-kms-crtc.h" @@ -51,6 +53,8 @@ struct _MetaKmsUpdate gboolean needs_modeset; MetaKmsImplDevice *impl_device; + + int sync_fd; }; void @@ -1136,6 +1140,8 @@ meta_kms_update_merge_from (MetaKmsUpdate *update, merge_custom_page_flip_from (update, other_update); merge_page_flip_listeners_from (update, other_update); merge_result_listeners_from (update, other_update); + + meta_kms_update_set_sync_fd (update, g_steal_fd (&other_update->sync_fd)); } gboolean @@ -1152,6 +1158,7 @@ meta_kms_update_new (MetaKmsDevice *device) update = g_new0 (MetaKmsUpdate, 1); update->device = device; update->is_latchable = TRUE; + update->sync_fd = -1; return update; } @@ -1175,6 +1182,7 @@ meta_kms_update_free (MetaKmsUpdate *update) g_list_free_full (update->crtc_color_updates, (GDestroyNotify) meta_kms_crtc_color_updates_free); g_clear_pointer (&update->custom_page_flip, meta_kms_custom_page_flip_free); + g_clear_fd (&update->sync_fd, NULL); g_free (update); } @@ -1200,6 +1208,23 @@ meta_kms_update_get_latch_crtc (MetaKmsUpdate *update) return update->latch_crtc; } +int +meta_kms_update_get_sync_fd (MetaKmsUpdate *update) +{ + return update->sync_fd; +} + +void +meta_kms_update_set_sync_fd (MetaKmsUpdate *update, + int sync_fd) +{ + if (update->sync_fd == sync_fd) + return; + + g_clear_fd (&update->sync_fd, NULL); + update->sync_fd = sync_fd; +} + gboolean meta_kms_update_is_empty (MetaKmsUpdate *update) { diff --git a/src/backends/native/meta-kms-update.h b/src/backends/native/meta-kms-update.h index 1951c80f60..ebb50cbaa8 100644 --- a/src/backends/native/meta-kms-update.h +++ b/src/backends/native/meta-kms-update.h @@ -39,7 +39,7 @@ typedef enum _MetaKmsAssignPlaneFlag META_KMS_ASSIGN_PLANE_FLAG_NONE = 0, META_KMS_ASSIGN_PLANE_FLAG_FB_UNCHANGED = 1 << 0, META_KMS_ASSIGN_PLANE_FLAG_ALLOW_FAIL = 1 << 1, - META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT = 1 << 2, + META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC = 1 << 2, } MetaKmsAssignPlaneFlag; struct _MetaKmsPageFlipListenerVtable @@ -157,6 +157,13 @@ void meta_kms_update_set_crtc_gamma (MetaKmsUpdate *update, MetaKmsCrtc *crtc, const MetaGammaLut *gamma); +int +meta_kms_update_get_sync_fd (MetaKmsUpdate *update); + +void +meta_kms_update_set_sync_fd (MetaKmsUpdate *update, + int sync_fd); + void meta_kms_plane_assignment_set_fb_damage (MetaKmsPlaneAssignment *plane_assignment, const int *rectangles, int n_rectangles); diff --git a/src/backends/native/meta-kms.c b/src/backends/native/meta-kms.c index d989337a10..4253a2adc8 100644 --- a/src/backends/native/meta-kms.c +++ b/src/backends/native/meta-kms.c @@ -63,6 +63,8 @@ struct _MetaKms int kernel_thread_inhibit_count; MetaKmsCursorManager *cursor_manager; + + gboolean shutting_down; }; G_DEFINE_TYPE (MetaKms, meta_kms, META_TYPE_THREAD) @@ -339,6 +341,12 @@ meta_kms_create_device (MetaKms *kms, return device; } +gboolean +meta_kms_is_shutting_down (MetaKms *kms) +{ + return kms->shutting_down; +} + static gpointer prepare_shutdown_in_impl (MetaThreadImpl *thread_impl, gpointer user_data, @@ -354,6 +362,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)); diff --git a/src/backends/native/meta-kms.h b/src/backends/native/meta-kms.h index 057c7a2348..0d6986529c 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 34709dccf1..c9162d31b8 100644 --- a/src/backends/native/meta-onscreen-native.c +++ b/src/backends/native/meta-onscreen-native.c @@ -29,6 +29,7 @@ #include "backends/native/meta-onscreen-native.h" +#include #include #include "backends/meta-egl-ext.h" @@ -76,7 +77,7 @@ typedef struct _MetaOnscreenNativeSecondaryGpuState struct { MetaDrmBufferDumb *current_dumb_fb; - MetaDrmBufferDumb *dumb_fbs[2]; + MetaDrmBufferDumb *dumb_fbs[3]; } cpu; gboolean noted_primary_gpu_copy_ok; @@ -102,12 +103,13 @@ struct _MetaOnscreenNative MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + ClutterFrame *presented_frame; + ClutterFrame *posted_frame; + ClutterFrame *stalled_frame; + ClutterFrame *next_frame; + struct { struct gbm_surface *surface; - MetaDrmBuffer *current_fb; - MetaDrmBuffer *next_fb; - CoglScanout *current_scanout; - CoglScanout *next_scanout; } gbm; #ifdef HAVE_EGL_DEVICE @@ -118,6 +120,9 @@ struct _MetaOnscreenNative } egl; #endif + gboolean needs_flush; + unsigned int swaps_pending; + gboolean frame_sync_requested; gboolean frame_sync_enabled; @@ -139,44 +144,37 @@ G_DEFINE_TYPE (MetaOnscreenNative, meta_onscreen_native, static GQuark blit_source_quark = 0; +static void +try_post_latest_swap (CoglOnscreen *onscreen); + +static void +post_finish_frame (MetaOnscreenNative *onscreen_native, + MetaKmsUpdate *kms_update); + 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); - g_clear_object (&onscreen_native->gbm.current_scanout); -} - static void meta_onscreen_native_swap_drm_fb (CoglOnscreen *onscreen) { MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - if (!onscreen_native->gbm.next_fb) + if (!onscreen_native->posted_frame) 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); - g_set_object (&onscreen_native->gbm.current_scanout, - onscreen_native->gbm.next_scanout); - g_clear_object (&onscreen_native->gbm.next_scanout); + g_clear_pointer (&onscreen_native->presented_frame, clutter_frame_unref); + onscreen_native->presented_frame = + g_steal_pointer (&onscreen_native->posted_frame); } static void -meta_onscreen_native_clear_next_fb (CoglOnscreen *onscreen) +meta_onscreen_native_clear_posted_fb (CoglOnscreen *onscreen) { MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); - g_clear_object (&onscreen_native->gbm.next_fb); - g_clear_object (&onscreen_native->gbm.next_scanout); + g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref); } static void @@ -214,7 +212,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_return_if_fail (info); _cogl_onscreen_notify_frame_sync (onscreen, info); _cogl_onscreen_notify_complete (onscreen, info); @@ -256,6 +254,7 @@ notify_view_crtc_presented (MetaRendererView *view, meta_onscreen_native_notify_frame_complete (onscreen); meta_onscreen_native_swap_drm_fb (onscreen); + try_post_latest_swap (onscreen); } static void @@ -305,15 +304,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 @@ -379,7 +376,8 @@ page_flip_feedback_discarded (MetaKmsCrtc *kms_crtc, } meta_onscreen_native_notify_frame_complete (onscreen); - meta_onscreen_native_clear_next_fb (onscreen); + meta_onscreen_native_clear_posted_fb (onscreen); + try_post_latest_swap (onscreen); } static const MetaKmsPageFlipListenerVtable page_flip_listener_vtable = { @@ -440,18 +438,36 @@ 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); + if (onscreen_native->swaps_pending <= 1) + return; + + onscreen_native->swaps_pending--; + + g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref); 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 apply_transform (MetaCrtcKms *crtc_kms, MetaKmsPlaneAssignment *kms_plane_assignment, @@ -528,12 +544,15 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, { MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaRendererNative *renderer_native = onscreen_native->renderer_native; + g_autoptr (ClutterFrame) frame = NULL; + MetaFrameNative *frame_native; MetaGpuKms *render_gpu = onscreen_native->render_gpu; MetaCrtcKms *crtc_kms = META_CRTC_KMS (crtc); MetaKmsCrtc *kms_crtc = meta_crtc_kms_get_kms_crtc (crtc_kms); MetaRendererNativeGpuData *renderer_gpu_data; MetaGpuKms *gpu_kms; MetaDrmBuffer *buffer; + CoglScanout *scanout; MetaKmsPlaneAssignment *plane_assignment; graphene_rect_t src_rect; MtkRectangle dst_rect; @@ -541,6 +560,9 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, COGL_TRACE_BEGIN_SCOPED (MetaOnscreenNativeFlipCrtcs, "Meta::OnscreenNative::flip_crtc()"); + frame = g_steal_pointer (&onscreen_native->next_frame); + g_return_if_fail (frame); + gpu_kms = META_GPU_KMS (meta_crtc_get_gpu (crtc)); g_assert (meta_gpu_kms_is_crtc_active (gpu_kms, crtc)); @@ -550,14 +572,14 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, switch (renderer_gpu_data->mode) { case META_RENDERER_NATIVE_MODE_GBM: - buffer = onscreen_native->gbm.next_fb; + frame_native = meta_frame_native_from_frame (frame); + buffer = meta_frame_native_get_buffer (frame_native); + scanout = meta_frame_native_get_scanout (frame_native); - if (onscreen_native->gbm.next_scanout) + if (scanout) { - cogl_scanout_get_src_rect (onscreen_native->gbm.next_scanout, - &src_rect); - cogl_scanout_get_dst_rect (onscreen_native->gbm.next_scanout, - &dst_rect); + cogl_scanout_get_src_rect (scanout, &src_rect); + cogl_scanout_get_dst_rect (scanout, &dst_rect); } else { @@ -601,6 +623,10 @@ meta_onscreen_native_flip_crtc (CoglOnscreen *onscreen, #endif } + g_warn_if_fail (!onscreen_native->posted_frame); + g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref); + onscreen_native->posted_frame = g_steal_pointer (&frame); + meta_kms_update_add_page_flip_listener (kms_update, kms_crtc, &page_flip_listener_vtable, @@ -849,29 +875,63 @@ import_shared_framebuffer (CoglOnscreen *onscreen, } static MetaDrmBuffer * -copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, - MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, - MetaRendererNativeGpuData *renderer_gpu_data, - gboolean *egl_context_changed, - MetaDrmBuffer *primary_gpu_fb) +copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, + MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state, + MetaRendererNativeGpuData *renderer_gpu_data, + MetaDrmBuffer *primary_gpu_fb, + GError **error) { 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); + CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); + CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); + CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); + CoglRendererEGL *cogl_renderer_egl = cogl_context->display->renderer->winsys; MetaRenderDevice *render_device; - EGLDisplay egl_display; - GError *error = NULL; + EGLDisplay egl_display = NULL; gboolean use_modifiers; MetaDeviceFile *device_file; MetaDrmBufferFlags flags; - MetaDrmBufferGbm *buffer_gbm; + MetaDrmBufferGbm *buffer_gbm = NULL; struct gbm_bo *bo; + EGLSync primary_gpu_egl_sync = EGL_NO_SYNC; + EGLSync secondary_gpu_egl_sync = EGL_NO_SYNC; + g_autofd int primary_gpu_sync_fence = EGL_NO_NATIVE_FENCE_FD_ANDROID; COGL_TRACE_BEGIN_SCOPED (CopySharedFramebufferSecondaryGpu, "copy_shared_framebuffer_gpu()"); if (renderer_gpu_data->secondary.needs_explicit_sync) - cogl_framebuffer_finish (COGL_FRAMEBUFFER (onscreen)); + { + if (!meta_egl_create_sync (egl, + cogl_renderer_egl->edpy, + EGL_SYNC_NATIVE_FENCE_ANDROID, + NULL, + &primary_gpu_egl_sync, + error)) + { + g_prefix_error (error, "Failed to create EGLSync on primary GPU: "); + return NULL; + } + + // According to the EGL_KHR_fence_sync specification we must ensure + // the fence command is flushed in this context to be able to await it + // in another (secondary GPU context) or we risk waiting indefinitely. + cogl_framebuffer_flush (COGL_FRAMEBUFFER (onscreen)); + + primary_gpu_sync_fence = + meta_egl_duplicate_native_fence_fd (egl, + cogl_renderer_egl->edpy, + primary_gpu_egl_sync, + error); + + if (primary_gpu_sync_fence == EGL_NO_NATIVE_FENCE_FD_ANDROID) + { + g_prefix_error (error, "Failed to duplicate EGLSync FD on primary GPU: "); + goto done; + } + } render_device = renderer_gpu_data->render_device; egl_display = meta_render_device_get_egl_display (render_device); @@ -881,15 +941,45 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, secondary_gpu_state->egl_surface, secondary_gpu_state->egl_surface, renderer_gpu_data->secondary.egl_context, - &error)) + error)) { - g_warning ("Failed to make current: %s", error->message); - g_error_free (error); - return NULL; + g_prefix_error (error, "Failed to make current: "); + goto done; } - *egl_context_changed = TRUE; + if (primary_gpu_sync_fence != EGL_NO_NATIVE_FENCE_FD_ANDROID) + { + EGLAttrib attribs[3]; + attribs[0] = EGL_SYNC_NATIVE_FENCE_FD_ANDROID; + attribs[1] = primary_gpu_sync_fence; + attribs[2] = EGL_NONE; + + if (!meta_egl_create_sync (egl, + egl_display, + EGL_SYNC_NATIVE_FENCE_ANDROID, + attribs, + &secondary_gpu_egl_sync, + error)) + { + g_prefix_error (error, "Failed to create EGLSync on secondary GPU: "); + goto done; + } + + // eglCreateSync takes ownership of an existing fd that is passed, so + // don't try to clean it up twice. + primary_gpu_sync_fence = EGL_NO_NATIVE_FENCE_FD_ANDROID; + + if (!meta_egl_wait_sync (egl, + egl_display, + secondary_gpu_egl_sync, + 0, + error)) + { + g_prefix_error (error, "Failed to wait for EGLSync on secondary GPU: "); + goto done; + } + } buffer_gbm = META_DRM_BUFFER_GBM (primary_gpu_fb); bo = meta_drm_buffer_gbm_get_bo (buffer_gbm); @@ -899,21 +989,19 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, renderer_gpu_data->secondary.egl_context, secondary_gpu_state->egl_surface, bo, - &error)) + error)) { - g_warning ("Failed to blit shared framebuffer: %s", error->message); - g_error_free (error); - return NULL; + g_prefix_error (error, "Failed to blit shared framebuffer: "); + goto done; } if (!meta_egl_swap_buffers (egl, egl_display, secondary_gpu_state->egl_surface, - &error)) + error)) { - g_warning ("Failed to swap buffers: %s", error->message); - g_error_free (error); - return NULL; + g_prefix_error (error, "Failed to swap buffers: "); + goto done; } use_modifiers = meta_renderer_native_use_modifiers (renderer_native); @@ -927,13 +1015,11 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, meta_drm_buffer_gbm_new_lock_front (device_file, secondary_gpu_state->gbm.surface, flags, - &error); + error); if (!buffer_gbm) { - g_warning ("meta_drm_buffer_gbm_new_lock_front failed: %s", - error->message); - g_error_free (error); - return NULL; + g_prefix_error (error, "meta_drm_buffer_gbm_new_lock_front failed: "); + goto done; } g_object_set_qdata_full (G_OBJECT (buffer_gbm), @@ -941,19 +1027,41 @@ copy_shared_framebuffer_gpu (CoglOnscreen *onscreen, g_object_ref (primary_gpu_fb), g_object_unref); - return META_DRM_BUFFER (buffer_gbm); +done: + _cogl_winsys_egl_ensure_current (cogl_display); + + if (primary_gpu_egl_sync != EGL_NO_SYNC && + !meta_egl_destroy_sync (egl, + cogl_renderer_egl->edpy, + primary_gpu_egl_sync, + error)) + g_prefix_error (error, "Failed to destroy primary GPU EGLSync: "); + + if (secondary_gpu_egl_sync != EGL_NO_SYNC && + !meta_egl_destroy_sync (egl, + egl_display, + secondary_gpu_egl_sync, + error)) + g_prefix_error (error, "Failed to destroy secondary GPU EGLSync: "); + + return buffer_gbm ? META_DRM_BUFFER (buffer_gbm) : NULL; } 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 * @@ -1193,56 +1301,54 @@ update_secondary_gpu_state_pre_swap_buffers (CoglOnscreen *onscreen, return copy; } -static void -update_secondary_gpu_state_post_swap_buffers (CoglOnscreen *onscreen, - gboolean *egl_context_changed, - MetaDrmBuffer *primary_gpu_fb, - MetaDrmBuffer **secondary_gpu_fb) +static MetaDrmBuffer * +acquire_front_buffer (CoglOnscreen *onscreen, + MetaDrmBuffer *primary_gpu_fb, + MetaDrmBuffer *secondary_gpu_fb, + GError **error) { MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaRendererNative *renderer_native = onscreen_native->renderer_native; MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; + MetaRendererNativeGpuData *renderer_gpu_data; + MetaDrmBuffer *imported_fb; COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeGpuStatePostSwapBuffers, - "update_secondary_gpu_state_post_swap_buffers()"); + "acquire_front_buffer()"); secondary_gpu_state = onscreen_native->secondary_gpu_state; - if (secondary_gpu_state) - { - MetaRendererNativeGpuData *renderer_gpu_data; - g_autoptr (MetaDrmBuffer) next_fb = NULL; + if (!secondary_gpu_state) + return g_object_ref (primary_gpu_fb); - renderer_gpu_data = - meta_renderer_native_get_gpu_data (renderer_native, - secondary_gpu_state->gpu_kms); - switch (renderer_gpu_data->secondary.copy_mode) - { - case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: - next_fb = import_shared_framebuffer (onscreen, + renderer_gpu_data = + meta_renderer_native_get_gpu_data (renderer_native, + secondary_gpu_state->gpu_kms); + switch (renderer_gpu_data->secondary.copy_mode) + { + case META_SHARED_FRAMEBUFFER_COPY_MODE_ZERO: + imported_fb = import_shared_framebuffer (onscreen, secondary_gpu_state, primary_gpu_fb); - if (next_fb) - break; - /* 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: - next_fb = g_object_ref (*secondary_gpu_fb); - break; - case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: - 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); + if (imported_fb) + return imported_fb; + /* 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: + return g_object_ref (secondary_gpu_fb); + case META_SHARED_FRAMEBUFFER_COPY_MODE_SECONDARY_GPU: + return copy_shared_framebuffer_gpu (onscreen, + secondary_gpu_state, + renderer_gpu_data, + primary_gpu_fb, + error); } + + g_assert_not_reached (); + return NULL; } static void @@ -1285,10 +1391,36 @@ 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); + } + + meta_onscreen_native_clear_posted_fb (onscreen); +} + +static void +assign_next_frame (MetaOnscreenNative *onscreen_native, + ClutterFrame *frame) +{ + CoglOnscreen *onscreen = COGL_ONSCREEN (onscreen_native); + + if (onscreen_native->next_frame != NULL) + { + g_warn_if_fail (onscreen_native->stalled_frame == NULL); + drop_stalled_swap (onscreen); + g_warn_if_fail (onscreen_native->stalled_frame == NULL); + g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref); + onscreen_native->stalled_frame = + g_steal_pointer (&onscreen_native->next_frame); + } + + onscreen_native->next_frame = clutter_frame_ref (frame); } static const MetaKmsResultListenerVtable swap_buffer_result_listener_vtable = { @@ -1304,37 +1436,41 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (onscreen); CoglContext *cogl_context = cogl_framebuffer_get_context (framebuffer); - CoglDisplay *cogl_display = cogl_context_get_display (cogl_context); 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); - MetaMonitorManager *monitor_manager = - meta_backend_get_monitor_manager (backend); MetaOnscreenNative *onscreen_native = META_ONSCREEN_NATIVE (onscreen); MetaOnscreenNativeSecondaryGpuState *secondary_gpu_state; 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 create_timestamp_query = TRUE; - 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; + g_autoptr (MetaDrmBuffer) buffer = NULL; COGL_TRACE_BEGIN_SCOPED (MetaRendererNativeSwapBuffers, "Meta::OnscreenNative::swap_buffers_with_damage()"); + if (meta_is_topic_enabled (META_DEBUG_KMS)) + { + unsigned int frames_pending = + cogl_onscreen_get_pending_frame_count (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, @@ -1384,46 +1520,28 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, g_warning ("Failed to lock front buffer on %s: %s", meta_device_file_get_path (render_device_file), error->message); - - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - meta_onscreen_native_notify_frame_complete (onscreen); - return; + goto swap_failed; } primary_gpu_fb = META_DRM_BUFFER (g_steal_pointer (&buffer_gbm)); - break; - case META_RENDERER_NATIVE_MODE_SURFACELESS: - g_assert_not_reached (); - break; -#ifdef HAVE_EGL_DEVICE - case META_RENDERER_NATIVE_MODE_EGL_DEVICE: - break; -#endif - } + buffer = acquire_front_buffer (onscreen, + primary_gpu_fb, + secondary_gpu_fb, + &error); + if (buffer == NULL) + { + g_warning ("Failed to acquire front buffer: %s", error->message); + goto swap_failed; + } - update_secondary_gpu_state_post_swap_buffers (onscreen, - &egl_context_changed, - primary_gpu_fb, - &secondary_gpu_fb); + meta_frame_native_set_buffer (frame_native, buffer); - switch (renderer_gpu_data->mode) - { - case META_RENDERER_NATIVE_MODE_GBM: - g_warn_if_fail (onscreen_native->gbm.next_fb == NULL); - if (onscreen_native->secondary_gpu_state) - g_set_object (&onscreen_native->gbm.next_fb, secondary_gpu_fb); - else - g_set_object (&onscreen_native->gbm.next_fb, primary_gpu_fb); - - if (!meta_drm_buffer_ensure_fb_id (onscreen_native->gbm.next_fb, &error)) + if (!meta_drm_buffer_ensure_fb_id (buffer, &error)) { g_warning ("Failed to ensure KMS FB ID on %s: %s", meta_device_file_get_path (render_device_file), error->message); - - frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; - meta_onscreen_native_notify_frame_complete (onscreen); - return; + goto swap_failed; } break; case META_RENDERER_NATIVE_MODE_SURFACELESS: @@ -1434,21 +1552,86 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, #endif } - /* - * 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 - * EGL_NO_CONTEXT current now, making cogl eventually set the correct - * context. - */ - if (egl_context_changed) - _cogl_winsys_egl_ensure_current (cogl_display); + assign_next_frame (onscreen_native, frame); - kms_crtc = meta_crtc_kms_get_kms_crtc (META_CRTC_KMS (onscreen_native->crtc)); - kms_device = meta_kms_crtc_get_device (kms_crtc); + clutter_frame_set_result (frame, + CLUTTER_FRAME_RESULT_PENDING_PRESENTED); + + meta_frame_native_set_damage (frame_native, rectangles, n_rectangles); + onscreen_native->swaps_pending++; + try_post_latest_swap (onscreen); + return; + +swap_failed: + frame_info->flags |= COGL_FRAME_INFO_FLAG_SYMBOLIC; + meta_onscreen_native_notify_frame_complete (onscreen); + clutter_frame_set_result (frame, CLUTTER_FRAME_RESULT_IDLE); +} + +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; + int sync_fd; + COGL_TRACE_SCOPED_ANCHOR (MetaRendererNativePostKmsUpdate); + + if (onscreen_native->next_frame == NULL || + onscreen_native->view == NULL || + meta_kms_is_shutting_down (kms)) + 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_get_pending_frame_count (onscreen); + unsigned int posts_pending; + int n_rectangles; + int *rectangles; + + 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 = clutter_frame_ref (onscreen_native->next_frame); + frame_native = meta_frame_native_from_frame (frame); + n_rectangles = meta_frame_native_get_damage (frame_native, &rectangles); + + 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, @@ -1470,13 +1653,11 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, { meta_renderer_native_queue_power_save_page_flip (renderer_native, onscreen); - clutter_frame_set_result (frame, - CLUTTER_FRAME_RESULT_PENDING_PRESENTED); return; } - COGL_TRACE_BEGIN_SCOPED (MetaRendererNativePostKmsUpdate, - "Meta::OnscreenNative::swap_buffers_with_damage#post_pending_update()"); + COGL_TRACE_BEGIN_ANCHORED (MetaRendererNativePostKmsUpdate, + "Meta::OnscreenNative::try_post_latest_swap#post_pending_update()"); switch (renderer_gpu_data->mode) { @@ -1491,8 +1672,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)) @@ -1506,8 +1685,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; @@ -1523,8 +1700,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; @@ -1537,9 +1712,10 @@ meta_onscreen_native_swap_buffers_with_damage (CoglOnscreen *onscreen, meta_kms_device_get_path (kms_device)); kms_update = meta_frame_native_steal_kms_update (frame_native); + sync_fd = cogl_context_get_latest_sync_fd (cogl_context); + meta_kms_update_set_sync_fd (kms_update, sync_fd); 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 @@ -1572,7 +1748,7 @@ meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, assign_primary_plane (crtc_kms, buffer, test_update, - META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT, + META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC, &src_rect, &dst_rect); @@ -1607,11 +1783,15 @@ scanout_result_feedback (const MetaKmsFeedback *kms_feedback, G_IO_ERROR_PERMISSION_DENIED)) { ClutterStageView *view = CLUTTER_STAGE_VIEW (onscreen_native->view); + ClutterFrame *posted_frame = onscreen_native->posted_frame; + MetaFrameNative *posted_frame_native = + meta_frame_native_from_frame (posted_frame); + CoglScanout *scanout = + meta_frame_native_get_scanout (posted_frame_native); g_warning ("Direct scanout page flip failed: %s", error->message); - cogl_scanout_notify_failed (onscreen_native->gbm.next_scanout, - onscreen); + cogl_scanout_notify_failed (scanout, onscreen); clutter_stage_view_add_redraw_clip (view, NULL); clutter_stage_view_schedule_update_now (view); } @@ -1620,7 +1800,7 @@ 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); + meta_onscreen_native_clear_posted_fb (onscreen); } static const MetaKmsResultListenerVtable scanout_result_listener_vtable = { @@ -1672,16 +1852,28 @@ 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_get_pending_frame_count (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); g_warn_if_fail (renderer_gpu_data->mode == META_RENDERER_NATIVE_MODE_GBM); - g_warn_if_fail (!onscreen_native->gbm.next_fb); - g_warn_if_fail (!onscreen_native->gbm.next_scanout); - g_set_object (&onscreen_native->gbm.next_scanout, scanout); - g_set_object (&onscreen_native->gbm.next_fb, - META_DRM_BUFFER (cogl_scanout_get_buffer (scanout))); + assign_next_frame (onscreen_native, frame); + + meta_frame_native_set_scanout (frame_native, scanout); + meta_frame_native_set_buffer (frame_native, + META_DRM_BUFFER (cogl_scanout_get_buffer (scanout))); frame_info->cpu_time_before_buffer_swap_us = g_get_monotonic_time (); @@ -1702,7 +1894,7 @@ meta_onscreen_native_direct_scanout (CoglOnscreen *onscreen, onscreen_native->view, onscreen_native->crtc, kms_update, - META_KMS_ASSIGN_PLANE_FLAG_DIRECT_SCANOUT, + META_KMS_ASSIGN_PLANE_FLAG_DISABLE_IMPLICIT_SYNC, NULL, 0); @@ -1787,11 +1979,15 @@ 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); + if (meta_get_debug_paint_flags () & META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY) + { + 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); + } + maybe_update_frame_sync (onscreen_native, frame); } @@ -1920,22 +2116,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_get_pending_frame_count (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_frame == NULL); + + g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref); + onscreen_native->next_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_frame != NULL); + + older_frame_native = + meta_frame_native_from_frame (onscreen_native->next_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, @@ -1958,7 +2211,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_pointer (&onscreen_native->stalled_frame, clutter_frame_unref); + g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref); } static gboolean @@ -2863,15 +3126,17 @@ meta_onscreen_native_dispose (GObject *object) meta_onscreen_native_detach (onscreen_native); + g_clear_pointer (&onscreen_native->next_frame, clutter_frame_unref); + g_clear_pointer (&onscreen_native->stalled_frame, clutter_frame_unref); + g_clear_pointer (&onscreen_native->posted_frame, clutter_frame_unref); + g_clear_pointer (&onscreen_native->presented_frame, clutter_frame_unref); + renderer_gpu_data = meta_renderer_native_get_gpu_data (renderer_native, onscreen_native->render_gpu); switch (renderer_gpu_data->mode) { case META_RENDERER_NATIVE_MODE_GBM: - g_clear_object (&onscreen_native->gbm.next_fb); - g_clear_object (&onscreen_native->gbm.next_scanout); - free_current_bo (onscreen); break; case META_RENDERER_NATIVE_MODE_SURFACELESS: g_assert_not_reached (); diff --git a/src/backends/native/meta-onscreen-native.h b/src/backends/native/meta-onscreen-native.h index 0e1193325a..e30357d19d 100644 --- a/src/backends/native/meta-onscreen-native.h +++ b/src/backends/native/meta-onscreen-native.h @@ -48,6 +48,8 @@ void meta_onscreen_native_dummy_power_save_page_flip (CoglOnscreen *onscreen); gboolean meta_onscreen_native_is_buffer_scanout_compatible (CoglOnscreen *onscreen, CoglScanout *scanout); +void meta_onscreen_native_discard_pending_swaps (CoglOnscreen *onscreen); + void meta_onscreen_native_set_view (CoglOnscreen *onscreen, MetaRendererView *view); diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c index 2fcf6dfd56..03b41dfcad 100644 --- a/src/backends/native/meta-renderer-native.c +++ b/src/backends/native/meta-renderer-native.c @@ -731,12 +731,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; @@ -748,6 +754,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 = @@ -1529,6 +1538,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) { @@ -1539,6 +1568,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); diff --git a/src/backends/native/meta-renderer-view-native.c b/src/backends/native/meta-renderer-view-native.c index a04e2e3533..5699f9a558 100644 --- a/src/backends/native/meta-renderer-view-native.c +++ b/src/backends/native/meta-renderer-view-native.c @@ -24,6 +24,7 @@ #include "backends/native/meta-renderer-view-native.h" +#include "backends/native/meta-crtc-native.h" #include "backends/native/meta-frame-native.h" struct _MetaRendererViewNative @@ -34,18 +35,58 @@ struct _MetaRendererViewNative G_DEFINE_TYPE (MetaRendererViewNative, meta_renderer_view_native, META_TYPE_RENDERER_VIEW) +static void +update_frame_clock_deadline_evasion (MetaRendererView *renderer_view) +{ + ClutterStageView *stage_view = CLUTTER_STAGE_VIEW (renderer_view); + ClutterFrameClock *frame_clock; + MetaCrtc *crtc; + MetaCrtcNative *crtc_native; + int64_t deadline_evasion_us; + + frame_clock = clutter_stage_view_get_frame_clock (stage_view); + crtc = meta_renderer_view_get_crtc (renderer_view); + crtc_native = META_CRTC_NATIVE (crtc); + + deadline_evasion_us = meta_crtc_native_get_deadline_evasion (crtc_native); + clutter_frame_clock_set_deadline_evasion (frame_clock, + deadline_evasion_us); +} + +static void +meta_renderer_view_native_constructed (GObject *object) +{ + MetaRendererView *renderer_view = META_RENDERER_VIEW (object); + + G_OBJECT_CLASS (meta_renderer_view_native_parent_class)->constructed (object); + + update_frame_clock_deadline_evasion (renderer_view); +} + static ClutterFrame * meta_renderer_view_native_new_frame (ClutterStageView *stage_view) { return (ClutterFrame *) meta_frame_native_new (); } +static void +meta_renderer_view_native_schedule_update (ClutterStageView *stage_view) +{ + MetaRendererView *renderer_view = META_RENDERER_VIEW (stage_view); + + update_frame_clock_deadline_evasion (renderer_view); +} + static void meta_renderer_view_native_class_init (MetaRendererViewNativeClass *klass) { + GObjectClass *object_class = G_OBJECT_CLASS (klass); ClutterStageViewClass *stage_view_class = CLUTTER_STAGE_VIEW_CLASS (klass); + object_class->constructed = meta_renderer_view_native_constructed; + stage_view_class->new_frame = meta_renderer_view_native_new_frame; + stage_view_class->schedule_update = meta_renderer_view_native_schedule_update; } static void diff --git a/src/core/util.c b/src/core/util.c index 05a0dea398..f8a08c747f 100644 --- a/src/core/util.c +++ b/src/core/util.c @@ -69,6 +69,7 @@ static const GDebugKey meta_debug_keys[] = { { "color", META_DEBUG_COLOR }, { "input-events", META_DEBUG_INPUT_EVENTS }, { "eis", META_DEBUG_EIS }, + { "kms-deadline", META_DEBUG_KMS_DEADLINE }, }; static gint verbose_topics = 0; @@ -326,6 +327,8 @@ meta_topic_to_string (MetaDebugTopic topic) return "INPUT_EVENTS"; case META_DEBUG_EIS: return "EIS"; + case META_DEBUG_KMS_DEADLINE: + return "KMS_DEADLINE"; } return "WM"; diff --git a/src/meta/meta-debug.h b/src/meta/meta-debug.h index b4c70144b7..2de9a89d75 100644 --- a/src/meta/meta-debug.h +++ b/src/meta/meta-debug.h @@ -50,6 +50,7 @@ * @META_DEBUG_COLOR: color management * @META_DEBUG_INPUT_EVENTS: input events * @META_DEBUG_EIS: eis state + * @META_DEBUG_KMS_DEADLINE: KMS deadline timers */ typedef enum { @@ -83,6 +84,7 @@ typedef enum META_DEBUG_COLOR = 1 << 26, META_DEBUG_INPUT_EVENTS = 1 << 27, META_DEBUG_EIS = 1 << 28, + META_DEBUG_KMS_DEADLINE = 1 << 29, } MetaDebugTopic; META_EXPORT diff --git a/src/meta/util.h b/src/meta/util.h index ca8ca2adeb..91ca6a8366 100644 --- a/src/meta/util.h +++ b/src/meta/util.h @@ -51,11 +51,14 @@ void meta_fatal (const char *format, * MetaDebugPaintFlag: * @META_DEBUG_PAINT_NONE: default * @META_DEBUG_PAINT_OPAQUE_REGION: paint opaque regions + * @META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY: make cursor updates await compositing + * frames */ typedef enum { META_DEBUG_PAINT_NONE = 0, META_DEBUG_PAINT_OPAQUE_REGION = 1 << 0, + META_DEBUG_PAINT_SYNC_CURSOR_PRIMARY = 1 << 1, } MetaDebugPaintFlag; META_EXPORT diff --git a/src/tests/meta-monitor-manager-test.c b/src/tests/meta-monitor-manager-test.c index f9275160c3..fd37eab380 100644 --- a/src/tests/meta-monitor-manager-test.c +++ b/src/tests/meta-monitor-manager-test.c @@ -517,6 +517,12 @@ meta_crtc_test_set_gamma_lut (MetaCrtc *crtc, sizeof (uint16_t) * lut->size); } +static int64_t +meta_crtc_test_get_deadline_evasion (MetaCrtcNative *crtc_native) +{ + return 0; +} + static void meta_crtc_test_finalize (GObject *object) { diff --git a/src/tests/native-kms-render.c b/src/tests/native-kms-render.c index f5ebc23fec..2f870fdc33 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 @@ -135,6 +139,7 @@ on_scanout_before_paint (ClutterStage *stage, CoglScanout *scanout; CoglScanoutBuffer *scanout_buffer; MetaDrmBuffer *buffer; + uint32_t fb_id; scanout = clutter_stage_view_peek_scanout (stage_view); if (!scanout) @@ -143,8 +148,13 @@ on_scanout_before_paint (ClutterStage *stage, scanout_buffer = cogl_scanout_get_buffer (scanout); g_assert_true (META_IS_DRM_BUFFER (scanout_buffer)); buffer = META_DRM_BUFFER (scanout_buffer); - 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 @@ -173,12 +183,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); @@ -197,15 +207,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 @@ -244,7 +280,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, }; @@ -270,7 +308,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"); @@ -292,10 +331,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); @@ -307,10 +351,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); @@ -364,6 +413,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)) @@ -401,6 +459,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)); } } @@ -410,11 +477,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 @@ -443,6 +510,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), };