From d5f68c8140dfd2dc2643cfd02b002cf8abee16b9 Mon Sep 17 00:00:00 2001 From: Dor Askayo Date: Tue, 4 Aug 2020 00:28:12 +0300 Subject: [PATCH] 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: --- clutter/clutter/clutter-frame-clock.c | 182 ++++++++++++++++++++++++-- clutter/clutter/clutter-frame-clock.h | 10 ++ 2 files changed, 180 insertions(+), 12 deletions(-) diff --git a/clutter/clutter/clutter-frame-clock.c b/clutter/clutter/clutter-frame-clock.c index d0864ed8f..302ca26eb 100644 --- a/clutter/clutter/clutter-frame-clock.c +++ b/clutter/clutter/clutter-frame-clock.c @@ -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 diff --git a/clutter/clutter/clutter-frame-clock.h b/clutter/clutter/clutter-frame-clock.h index 825776abe..a7be5ef31 100644 --- a/clutter/clutter/clutter-frame-clock.h +++ b/clutter/clutter/clutter-frame-clock.h @@ -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);