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:
parent
6cee9410f5
commit
d5f68c8140
@ -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
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user