clutter/frame-clock: Allow scheduling an update in the future

Allow creating a queue of future times to tick at. This adds a new clock
state where we let the clock go idle, but will wake at a time in the
future to tick.

We can still schedule ticks while in this new state, but we're assured a
tick at or shortly after the scheduled time.

This will be used later by wayland code that schedules future presentation.

Signed-off-by: Derek Foreman <derek.foreman@collabora.com>
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3355>
This commit is contained in:
Derek Foreman 2024-10-07 06:07:48 -05:00
parent 8c1ec168c6
commit 5b214dc2b7
2 changed files with 176 additions and 0 deletions

View File

@ -52,6 +52,11 @@ typedef struct _ClutterFrameListener
gpointer user_data;
} ClutterFrameListener;
typedef struct _DeferredTime
{
int64_t target_time_us;
} DeferredTime;
typedef struct _ClutterClockSource
{
GSource source;
@ -70,6 +75,7 @@ typedef enum _ClutterFrameClockState
CLUTTER_FRAME_CLOCK_STATE_IDLE,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW,
CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER,
CLUTTER_FRAME_CLOCK_STATE_DISPATCHING,
CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED,
} ClutterFrameClockState;
@ -145,11 +151,17 @@ struct _ClutterFrameClock
int64_t deadline_evasion_us;
char *output_name;
GQueue *deferred_times;
};
G_DEFINE_TYPE (ClutterFrameClock, clutter_frame_clock,
G_TYPE_OBJECT)
static void
clutter_frame_clock_schedule_update_later (ClutterFrameClock *frame_clock,
int64_t target_us);
float
clutter_frame_clock_get_refresh_rate (ClutterFrameClock *frame_clock)
{
@ -273,9 +285,35 @@ advance_timelines (ClutterFrameClock *frame_clock,
g_list_free_full (timelines, g_object_unref);
}
static gboolean
clean_deferred_times (ClutterFrameClock *frame_clock)
{
DeferredTime *head;
gboolean cleaned_times = FALSE;
int64_t current_time_us;
if (frame_clock->is_next_presentation_time_valid)
current_time_us = frame_clock->next_presentation_time_us;
else
current_time_us = g_get_monotonic_time ();
while ((head = g_queue_peek_head (frame_clock->deferred_times)))
{
if (current_time_us < head->target_time_us)
break;
g_free (g_queue_pop_head (frame_clock->deferred_times));
cleaned_times = TRUE;
}
return cleaned_times;
}
static void
maybe_reschedule_update (ClutterFrameClock *frame_clock)
{
DeferredTime *head;
gboolean cleaned;
if (frame_clock->pending_reschedule ||
frame_clock->timelines)
{
@ -290,6 +328,20 @@ maybe_reschedule_update (ClutterFrameClock *frame_clock)
{
clutter_frame_clock_schedule_update (frame_clock);
}
return;
}
cleaned = clean_deferred_times (frame_clock);
head = g_queue_peek_head (frame_clock->deferred_times);
if (head)
{
clutter_frame_clock_schedule_update_later (frame_clock,
head->target_time_us);
}
else if (cleaned)
{
clutter_frame_clock_schedule_update (frame_clock);
}
}
@ -474,6 +526,7 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
g_warn_if_reached ();
break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
@ -498,6 +551,7 @@ clutter_frame_clock_notify_ready (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
g_warn_if_reached ();
break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
@ -798,6 +852,7 @@ clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock)
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
frame_clock->pending_reschedule = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break;
@ -843,6 +898,7 @@ 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:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
return;
@ -898,6 +954,7 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
return;
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
@ -935,6 +992,113 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED;
}
static void
clutter_frame_clock_schedule_update_later (ClutterFrameClock *frame_clock,
int64_t target_us)
{
int64_t next_update_time_us = -1;
int64_t next_presentation_time_us;
int64_t next_frame_deadline_us;
int64_t ready_time_us = 0, extrapolated_presentation_time_us;
int64_t max_render_time_us;
int64_t cycles;
if (frame_clock->inhibit_count > 0)
{
frame_clock->pending_reschedule = TRUE;
return;
}
switch (frame_clock->state)
{
case CLUTTER_FRAME_CLOCK_STATE_INIT:
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
return;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
case CLUTTER_FRAME_CLOCK_STATE_PENDING_PRESENTED:
frame_clock->pending_reschedule = TRUE;
frame_clock->pending_reschedule_now = TRUE;
return;
}
switch (frame_clock->mode)
{
case CLUTTER_FRAME_CLOCK_MODE_FIXED:
calculate_next_update_time_us (frame_clock,
&next_update_time_us,
&next_presentation_time_us,
&next_frame_deadline_us);
break;
case CLUTTER_FRAME_CLOCK_MODE_VARIABLE:
calculate_next_variable_update_time_us (frame_clock,
&next_update_time_us,
&next_presentation_time_us,
&next_frame_deadline_us);
break;
}
g_warn_if_fail (next_presentation_time_us != -1);
if (next_presentation_time_us >= target_us)
{
clutter_frame_clock_schedule_update (frame_clock);
return;
}
switch (frame_clock->mode)
{
case CLUTTER_FRAME_CLOCK_MODE_FIXED:
cycles =
(target_us - next_presentation_time_us +
frame_clock->refresh_interval_us - 1) /
frame_clock->refresh_interval_us;
extrapolated_presentation_time_us =
next_presentation_time_us + frame_clock->refresh_interval_us * cycles;
max_render_time_us = next_presentation_time_us - next_frame_deadline_us;
ready_time_us = extrapolated_presentation_time_us - max_render_time_us;
break;
case CLUTTER_FRAME_CLOCK_MODE_VARIABLE:
max_render_time_us =
clutter_frame_clock_compute_max_render_time_us (frame_clock);
ready_time_us = target_us - max_render_time_us;
break;
}
g_source_set_ready_time (frame_clock->source, ready_time_us);
frame_clock->pending_reschedule = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER;
}
static int
compare_times (const DeferredTime *a,
const DeferredTime *b,
void *data)
{
if (a->target_time_us > b->target_time_us)
return 1;
if (a->target_time_us < b->target_time_us)
return -1;
return 0;
}
void
clutter_frame_clock_add_future_time (ClutterFrameClock *frame_clock,
int64_t when_us)
{
DeferredTime *time = g_new (DeferredTime, 1);
time->target_time_us = when_us;
g_queue_insert_sorted (frame_clock->deferred_times, time,
(GCompareDataFunc)compare_times, NULL);
maybe_reschedule_update (frame_clock);
}
void
clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
ClutterFrameClockMode mode)
@ -950,6 +1114,7 @@ clutter_frame_clock_set_mode (ClutterFrameClock *frame_clock,
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
frame_clock->pending_reschedule = TRUE;
frame_clock->state = CLUTTER_FRAME_CLOCK_STATE_IDLE;
break;
@ -1081,6 +1246,7 @@ clutter_frame_clock_dispatch (ClutterFrameClock *frame_clock,
case CLUTTER_FRAME_CLOCK_STATE_IDLE:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_NOW:
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED_LATER:
break;
case CLUTTER_FRAME_CLOCK_STATE_DISPATCHING:
switch (result)
@ -1292,6 +1458,8 @@ clutter_frame_clock_new (float refresh_rate,
frame_clock->output_name = g_strdup (output_name);
frame_clock->deferred_times = g_queue_new ();
return frame_clock;
}
@ -1318,6 +1486,10 @@ clutter_frame_clock_dispose (GObject *object)
g_clear_pointer (&frame_clock->output_name, g_free);
if (frame_clock->deferred_times)
g_queue_free_full (g_steal_pointer (&frame_clock->deferred_times), g_free);
frame_clock->deferred_times = NULL;
G_OBJECT_CLASS (clutter_frame_clock_parent_class)->dispose (object);
}

View File

@ -87,6 +87,10 @@ void clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock);
CLUTTER_EXPORT
void clutter_frame_clock_schedule_update_now (ClutterFrameClock *frame_clock);
CLUTTER_EXPORT
void clutter_frame_clock_add_future_time (ClutterFrameClock *frame_clock,
int64_t when_us);
CLUTTER_EXPORT
void clutter_frame_clock_inhibit (ClutterFrameClock *frame_clock);