1
0
Fork 0

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.

(cherry picked from commit 280f7f6b26cd3e7a82706d1d001419295ea15d8b)
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1441>
Signed-off-by: Mingi Sung <sungmg@saltyming.net>
This commit is contained in:
Daniel van Vugt 2021-09-07 19:15:55 +08:00 committed by Mingi Sung
parent 06d2bca831
commit 0615b2376b
Signed by: sungmg
GPG key ID: 41BAFD6FFD8036C5

View file

@ -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.875f
#define MINIMUM_REFRESH_RATE 30.f
@ -905,6 +914,35 @@ 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:
{
const Frame *last_flip;
if (frame_clock->mode == CLUTTER_FRAME_CLOCK_MODE_VARIABLE)
return FALSE;
last_flip = frame_clock->prev_dispatch;
if (!last_flip)
return TRUE;
return !(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)
{
@ -927,11 +965,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;
@ -967,6 +1012,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)
{
@ -990,7 +1040,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;
@ -1017,6 +1083,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;
@ -1438,6 +1505,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;