From 85a4c97ae51280ed86a078b4792eeb127bd7d4d8 Mon Sep 17 00:00:00 2001 From: Daniel van Vugt Date: Tue, 7 Sep 2021 19:15:55 +0800 Subject: [PATCH] clutter/frame-clock: Conditionally disable triple buffering 1. When direct scanout is attempted There's no compositing during direct scanout so the "render" time is zero. Thus there is no need to implement triple buffering for direct scanouts. Stick to double buffering and enjoy the lower latency. 2. If disabled by environment variable MUTTER_DEBUG_TRIPLE_BUFFERING With possible values {never, auto, always} where auto is the default. 3. When VRR is in use VRR calls `clutter_frame_clock_schedule_update_now` which would keep the buffer queue full, which in turn prevented direct scanout mode. Because OnscreenNative currently only supports direct scanout with double buffering. We now break that feedback loop by preventing triple buffering from being scheduled when the frame clock mode becomes variable. Long term this could also be solved by supporting triple buffering in direct scanout mode. But whether or not that would be desirable given the latency penalty remains to be seen. Signed-off-by: Mingi Sung --- clutter/clutter/clutter-frame-clock.c | 70 ++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index 4d4c33458..4ad834e02 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -42,6 +42,15 @@ enum static guint signals[N_SIGNALS]; +typedef enum +{ + TRIPLE_BUFFERING_MODE_NEVER, + TRIPLE_BUFFERING_MODE_AUTO, + TRIPLE_BUFFERING_MODE_ALWAYS, +} TripleBufferingMode; + +static TripleBufferingMode triple_buffering_mode = TRIPLE_BUFFERING_MODE_AUTO; + #define SYNC_DELAY_FALLBACK_FRACTION 0.875 #define MINIMUM_REFRESH_RATE 30.f @@ -858,6 +867,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) { @@ -880,11 +908,18 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW: case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: return; - case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: 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; @@ -920,6 +955,11 @@ 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) { @@ -943,7 +983,23 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock) case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED_NOW: return; case CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE: - frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_DISPATCHED_ONE_AND_SCHEDULED; + 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; @@ -970,6 +1026,7 @@ 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; @@ -1385,6 +1442,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;