clutter/frame-clock: Handle immediate present feedback

In certain scenarios, the frame clock needs to handle present feedback
long before the assumed presentation time happens. To avoid scheduling
the next frame to soon, avoid scheduling one if we were presented half a
frame interval within the last expected presentation time.

https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1285
This commit is contained in:
Jonas Ådahl 2020-03-20 16:03:34 +01:00
parent 96a108ed4e
commit e743b36cfc
2 changed files with 95 additions and 2 deletions

View File

@ -72,6 +72,9 @@ struct _ClutterFrameClock
ClutterFrameClockState state; ClutterFrameClockState state;
int64_t last_presentation_time_us; int64_t last_presentation_time_us;
gboolean is_next_presentation_time_valid;
int64_t next_presentation_time_us;
gboolean pending_reschedule; gboolean pending_reschedule;
}; };
@ -120,7 +123,8 @@ clutter_frame_clock_notify_presented (ClutterFrameClock *frame_clock,
static void static void
calculate_next_update_time_us (ClutterFrameClock *frame_clock, calculate_next_update_time_us (ClutterFrameClock *frame_clock,
int64_t *out_next_update_time_us) int64_t *out_next_update_time_us,
int64_t *out_next_presentation_time_us)
{ {
int64_t last_presentation_time_us; int64_t last_presentation_time_us;
int64_t now_us; int64_t now_us;
@ -128,6 +132,8 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
int64_t refresh_interval_us; int64_t refresh_interval_us;
int64_t min_render_time_allowed_us; int64_t min_render_time_allowed_us;
int64_t max_render_time_allowed_us; int64_t max_render_time_allowed_us;
int64_t last_next_presentation_time_us;
int64_t time_since_last_next_presentation_time_us;
int64_t next_presentation_time_us; int64_t next_presentation_time_us;
int64_t next_update_time_us; int64_t next_update_time_us;
@ -159,12 +165,24 @@ calculate_next_update_time_us (ClutterFrameClock *frame_clock,
next_presentation_time_us = logical_clock_phase_us + hw_clock_offset_us; next_presentation_time_us = logical_clock_phase_us + hw_clock_offset_us;
} }
/* Skip one interval if we got an early presented event. */
last_next_presentation_time_us = frame_clock->next_presentation_time_us;
time_since_last_next_presentation_time_us =
next_presentation_time_us - last_next_presentation_time_us;
if (frame_clock->is_next_presentation_time_valid &&
time_since_last_next_presentation_time_us < (refresh_interval_us / 2))
{
next_presentation_time_us =
frame_clock->next_presentation_time_us + refresh_interval_us;
}
while (next_presentation_time_us < now_us + min_render_time_allowed_us) while (next_presentation_time_us < now_us + min_render_time_allowed_us)
next_presentation_time_us += refresh_interval_us; next_presentation_time_us += refresh_interval_us;
next_update_time_us = next_presentation_time_us - max_render_time_allowed_us; next_update_time_us = next_presentation_time_us - max_render_time_allowed_us;
*out_next_update_time_us = next_update_time_us; *out_next_update_time_us = next_update_time_us;
*out_next_presentation_time_us = next_presentation_time_us;
} }
void void
@ -178,7 +196,10 @@ clutter_frame_clock_schedule_update (ClutterFrameClock *frame_clock)
next_update_time_us = g_get_monotonic_time (); next_update_time_us = g_get_monotonic_time ();
break; break;
case CLUTTER_FRAME_CLOCK_STATE_IDLE: case CLUTTER_FRAME_CLOCK_STATE_IDLE:
calculate_next_update_time_us (frame_clock, &next_update_time_us); calculate_next_update_time_us (frame_clock,
&next_update_time_us,
&frame_clock->next_presentation_time_us);
frame_clock->is_next_presentation_time_valid = TRUE;
break; break;
case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED: case CLUTTER_FRAME_CLOCK_STATE_SCHEDULED:
return; return;

View File

@ -158,6 +158,78 @@ frame_clock_schedule_update (void)
g_source_unref (source); g_source_unref (source);
} }
static gboolean
schedule_update_idle (gpointer user_data)
{
ClutterFrameClock *frame_clock = user_data;
clutter_frame_clock_schedule_update (frame_clock);
return G_SOURCE_REMOVE;
}
static ClutterFrameResult
immediate_frame_clock_frame (ClutterFrameClock *frame_clock,
int64_t frame_count,
gpointer user_data)
{
GMainLoop *main_loop = user_data;
g_assert_cmpint (frame_count, ==, expected_frame_count);
expected_frame_count++;
if (test_frame_count == 0)
{
g_main_loop_quit (main_loop);
return CLUTTER_FRAME_RESULT_IDLE;
}
test_frame_count--;
clutter_frame_clock_notify_presented (frame_clock, g_get_monotonic_time ());
g_idle_add (schedule_update_idle, frame_clock);
return CLUTTER_FRAME_RESULT_PENDING_PRESENTED;
}
static const ClutterFrameListenerIface immediate_frame_listener_iface = {
.frame = immediate_frame_clock_frame,
};
static void
frame_clock_immediate_present (void)
{
GMainLoop *main_loop;
ClutterFrameClock *frame_clock;
int64_t before_us;
int64_t after_us;
test_frame_count = 10;
expected_frame_count = 0;
main_loop = g_main_loop_new (NULL, FALSE);
frame_clock = clutter_frame_clock_new (refresh_rate,
&immediate_frame_listener_iface,
main_loop);
before_us = g_get_monotonic_time ();
clutter_frame_clock_schedule_update (frame_clock);
g_main_loop_run (main_loop);
after_us = g_get_monotonic_time ();
/* The initial frame will only be delayed by 2 ms, so we are checking one
* less.
*/
g_assert_cmpint (after_us - before_us, >, 9 * refresh_interval_us);
g_main_loop_unref (main_loop);
g_object_unref (frame_clock);
}
CLUTTER_TEST_SUITE ( CLUTTER_TEST_SUITE (
CLUTTER_TEST_UNIT ("/frame-clock/schedule-update", frame_clock_schedule_update) CLUTTER_TEST_UNIT ("/frame-clock/schedule-update", frame_clock_schedule_update)
CLUTTER_TEST_UNIT ("/frame-clock/immediate-present", frame_clock_immediate_present)
) )