clutter/frame-clock: Add a mode for variable scheduling

A new variable scheduling mode is introduced which allows lower
priority updates to be scheduled on a timeout which represents a lower
refresh rate, while allowing high priority updates to be scheduled to
occur as soon as possible.

This mode will be used by following commits to implement
synchronization of page flips to the update rate of specifc surface
actors.

High priorty updates are either scheduled to occur "now" if they
arrive at a rate which is lower than the maximum refresh rate, or
according to the measured maximum render time if they arrive at a
rate which meets or exceeds the maximum refresh rate. This approach
allows achieving low input latency in both scenarios.

Seperate handling for low priority updates is needed to avoid visible
stutter in the content of the surface that drives the refresh rate. An
example for a low priority update is cursor movement when the KMS
deadline timer is disabled.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1154>
This commit is contained in:
Dor Askayo 2020-08-04 00:28:12 +03:00 committed by Marge Bot
parent 6cee9410f5
commit d5f68c8140
2 changed files with 180 additions and 12 deletions

View File

@ -37,6 +37,8 @@ static guint signals[N_SIGNALS];
#define SYNC_DELAY_FALLBACK_FRACTION 0.875
#define MINIMUM_REFRESH_RATE 30.f
typedef struct _ClutterFrameListener
{
const ClutterFrameListenerIface *iface;
@ -66,6 +68,8 @@ struct _ClutterFrameClock
float refresh_rate;
int64_t refresh_interval_us;
int64_t minimum_refresh_interval_us;
ClutterFrameListener listener;
GSource *source;
@ -73,6 +77,8 @@ struct _ClutterFrameClock
int64_t frame_count;
ClutterFrameClockState state;
ClutterFrameClockMode mode;
int64_t last_dispatch_time_us;
int64_t last_dispatch_lateness_us;
int64_t last_presentation_time_us;
@ -607,6 +613,92 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
*out_next_frame_deadline_us = next_presentation_time_us - min_render_time_allowed_us;
}
static void
calculate_next_variable_update_time_us (ClutterFrameClock *frame_clock,
int64_t *out_next_update_time_us,
int64_t *out_next_presentation_time_us,
int64_t *out_next_frame_deadline_us)
{
int64_t last_presentation_time_us;
int64_t now_us;
int64_t refresh_interval_us;
int64_t max_render_time_allowed_us;
int64_t next_presentation_time_us;
int64_t next_update_time_us;
int64_t next_frame_deadline_us;
now_us = g_get_monotonic_time ();
refresh_interval_us = frame_clock->refresh_interval_us;
if (frame_clock->last_presentation_time_us == 0)
{
*out_next_update_time_us =
frame_clock->last_dispatch_time_us ?
((frame_clock->last_dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + refresh_interval_us) :
now_us;
*out_next_presentation_time_us = 0;
*out_next_frame_deadline_us = 0;
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;
next_update_time_us = next_presentation_time_us - max_render_time_allowed_us;
if (next_update_time_us < now_us)
next_update_time_us = now_us;
if (next_presentation_time_us < next_update_time_us)
next_presentation_time_us = 0;
next_frame_deadline_us = next_update_time_us;
if (next_frame_deadline_us == now_us)
next_frame_deadline_us += refresh_interval_us;
*out_next_update_time_us = next_update_time_us;
*out_next_presentation_time_us = next_presentation_time_us;
*out_next_frame_deadline_us = next_frame_deadline_us;
}
static void
calculate_next_variable_update_timeout_us (ClutterFrameClock *frame_clock,
int64_t *out_next_update_time_us)
{
int64_t now_us;
int64_t last_presentation_time_us;
int64_t next_presentation_time_us;
int64_t timeout_interval_us;
now_us = g_get_monotonic_time ();
last_presentation_time_us = frame_clock->last_presentation_time_us;
timeout_interval_us = frame_clock->minimum_refresh_interval_us;
if (last_presentation_time_us == 0)
{
*out_next_update_time_us =
frame_clock->last_dispatch_time_us ?
((frame_clock->last_dispatch_time_us -
frame_clock->last_dispatch_lateness_us) + timeout_interval_us) :
now_us;
return;
}
next_presentation_time_us = last_presentation_time_us + timeout_interval_us;
while (next_presentation_time_us < now_us)
next_presentation_time_us += timeout_interval_us;
*out_next_update_time_us = next_presentation_time_us;
}
void
clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock)
{
@ -665,7 +757,6 @@ 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:
next_update_time_us = g_get_monotonic_time ();
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
return;
@ -676,13 +767,30 @@ clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock)
return;
}
switch (frame_clock->mode)
{
case CLUTTER_FRAME_CLOCK_MODE_FIXED:
next_update_time_us = g_get_monotonic_time ();
frame_clock->is_next_presentation_time_valid = FALSE;
frame_clock->has_next_frame_deadline = FALSE;
break;
case CLUTTER_FRAME_CLOCK_MODE_VARIABLE:
calculate_next_variable_update_time_us (frame_clock,
&next_update_time_us,
&frame_clock->next_presentation_time_us,
&frame_clock->next_frame_deadline_us);
frame_clock->is_next_presentation_time_valid =
(frame_clock->next_presentation_time_us != 0);
frame_clock->has_next_frame_deadline =
(frame_clock->next_frame_deadline_us != 0);
break;
}
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_NOW;
frame_clock->is_next_presentation_time_valid = FALSE;
frame_clock->has_next_frame_deadline = FALSE;
}
void
@ -700,16 +808,10 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
{
case CLUTTER_FRAME_CLOCK_STATE_INIT:
next_update_time_us = g_get_monotonic_time ();
break;
g_source_set_ready_time (frame_clock->source, next_update_time_us);
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
return;
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
calculate_next_update_time_us (frame_clock,
&next_update_time_us,
&frame_clock->next_presentation_time_us,
&frame_clock->next_frame_deadline_us);
frame_clock->is_next_presentation_time_valid =
(frame_clock->next_presentation_time_us != 0);
frame_clock->has_next_frame_deadline =
(frame_clock->next_frame_deadline_us != 0);
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
@ -720,6 +822,26 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
return;
}
switch (frame_clock->mode)
{
case CLUTTER_FRAME_CLOCK_MODE_FIXED:
calculate_next_update_time_us (frame_clock,
&next_update_time_us,
&frame_clock->next_presentation_time_us,
&frame_clock->next_frame_deadline_us);
frame_clock->is_next_presentation_time_valid =
(frame_clock->next_presentation_time_us != 0);
frame_clock->has_next_frame_deadline =
(frame_clock->next_frame_deadline_us != 0);
break;
case CLUTTER_FRAME_CLOCK_MODE_VARIABLE:
calculate_next_variable_update_timeout_us (frame_clock,
&next_update_time_us);
frame_clock->is_next_presentation_time_valid = FALSE;
frame_clock->has_next_frame_deadline = FALSE;
break;
}
g_warn_if_fail (next_update_time_us != -1);
frame_clock->next_update_time_us = next_update_time_us;
@ -727,6 +849,37 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
}
void
clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
ClutterFrameClockMode mode)
{
if (frame_clock->mode == mode)
return;
frame_clock->mode = mode;
switch (frame_clock->state)
{
case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
frame_clock->pending_reschedule = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
frame_clock->pending_reschedule = TRUE;
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:
break;
}
maybe_reschedule_update (frame_clock);
}
static void
clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
int64_t time_us)
@ -944,6 +1097,10 @@ clutter_frame_clock_new (float refresh_rate,
init_frame_clock_source (frame_clock);
clutter_frame_clock_set_refresh_rate (frame_clock, refresh_rate);
frame_clock->minimum_refresh_interval_us =
(int64_t) (0.5 + G_USEC_PER_SEC / MINIMUM_REFRESH_RATE);
frame_clock->vblank_duration_us = vblank_duration_us;
frame_clock->output_name = g_strdup (output_name);
@ -981,6 +1138,7 @@ static void
clutter_frame_clock_init (ClutterFrameClock *frame_clock)
{
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_INIT;
frame_clock->mode = CLUTTER_FRAME_CLOCK_MODE_FIXED;
}
static void

View File

@ -54,6 +54,12 @@ typedef struct _ClutterFrameListenerIface
gpointer user_data);
} ClutterFrameListenerIface;
typedef enum _ClutterFrameClockMode
{
CLUTTER_FRAME_CLOCK_MODE_FIXED,
CLUTTER_FRAME_CLOCK_MODE_VARIABLE,
} ClutterFrameClockMode;
CLUTTER_EXPORT
ClutterFrameClock * clutter_frame_clock_new (float refresh_rate,
int64_t vblank_duration_us,
@ -64,6 +70,10 @@ ClutterFrameClock * clutter_frame_clock_new (float re
CLUTTER_EXPORT
void clutter_frame_clock_destroy (ClutterFrameClock *frame_clock);
CLUTTER_EXPORT
void clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
ClutterFrameClockMode mode);
CLUTTER_EXPORT
void clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
ClutterFrameInfo *frame_info);