diff --git a/gdk/gdkframeclock.c b/gdk/gdkframeclock.c index ecb84bd8aa..fd41d94533 100644 --- a/gdk/gdkframeclock.c +++ b/gdk/gdkframeclock.c @@ -378,3 +378,76 @@ gdk_frame_clock_frame_requested (GdkFrameClock *clock) g_signal_emit (G_OBJECT (clock), signals[FRAME_REQUESTED], 0); } + +GdkFrameTimings * +gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock) +{ + GdkFrameHistory *history; + gint64 frame_counter; + + g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0); + + history = gdk_frame_clock_get_history (clock); + frame_counter = gdk_frame_history_get_frame_counter (history); + return gdk_frame_history_get_timings (history, frame_counter); +} + + +#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */ +#define MAX_HISTORY_AGE 150000 /* 150ms */ + +void +gdk_frame_clock_get_refresh_info (GdkFrameClock *clock, + gint64 base_time, + gint64 *refresh_interval_return, + gint64 *presentation_time_return) +{ + GdkFrameHistory *history; + gint64 frame_counter; + + g_return_if_fail (GDK_IS_FRAME_CLOCK (clock)); + + history = gdk_frame_clock_get_history (clock); + frame_counter = gdk_frame_history_get_frame_counter (history); + + if (presentation_time_return) + *presentation_time_return = 0; + if (refresh_interval_return) + *refresh_interval_return = DEFAULT_REFRESH_INTERVAL; + + while (TRUE) + { + GdkFrameTimings *timings = gdk_frame_history_get_timings (history, frame_counter); + gint64 presentation_time; + gint64 refresh_interval; + + if (timings == NULL) + return; + + refresh_interval = gdk_frame_timings_get_refresh_interval (timings); + presentation_time = gdk_frame_timings_get_presentation_time (timings); + + if (presentation_time != 0) + { + if (presentation_time > base_time - MAX_HISTORY_AGE && + presentation_time_return) + { + if (refresh_interval == 0) + refresh_interval = DEFAULT_REFRESH_INTERVAL; + + if (refresh_interval_return) + *refresh_interval_return = refresh_interval; + + while (presentation_time < base_time) + presentation_time += refresh_interval; + + if (presentation_time_return) + *presentation_time_return = presentation_time; + } + + return; + } + + frame_counter--; + } +} diff --git a/gdk/gdkframeclock.h b/gdk/gdkframeclock.h index 03b04304cd..8d79f90a96 100644 --- a/gdk/gdkframeclock.h +++ b/gdk/gdkframeclock.h @@ -78,7 +78,7 @@ struct _GdkFrameClockInterface { GTypeInterface base_iface; - guint64 (* get_frame_time) (GdkFrameClock *clock); + guint64 (* get_frame_time) (GdkFrameClock *clock); void (* request_phase) (GdkFrameClock *clock, GdkFrameClockPhase phase); @@ -102,7 +102,7 @@ struct _GdkFrameClockInterface GType gdk_frame_clock_get_type (void) G_GNUC_CONST; -guint64 gdk_frame_clock_get_frame_time (GdkFrameClock *clock); +guint64 gdk_frame_clock_get_frame_time (GdkFrameClock *clock); void gdk_frame_clock_request_phase (GdkFrameClock *clock, GdkFrameClockPhase phase); @@ -117,6 +117,13 @@ GdkFrameHistory *gdk_frame_clock_get_history (GdkFrameClock *clock); void gdk_frame_clock_get_frame_time_val (GdkFrameClock *clock, GTimeVal *timeval); +void gdk_frame_clock_get_refresh_info (GdkFrameClock *clock, + gint64 base_time, + gint64 *refresh_interval_return, + gint64 *presentation_time_return); + +GdkFrameTimings *gdk_frame_clock_get_current_frame_timings (GdkFrameClock *clock); + /* Signal emitters (used in frame clock implementations) */ void gdk_frame_clock_frame_requested (GdkFrameClock *clock); diff --git a/gdk/gdkframeclockidle.c b/gdk/gdkframeclockidle.c index 09624c45b0..47103703cd 100644 --- a/gdk/gdkframeclockidle.c +++ b/gdk/gdkframeclockidle.c @@ -204,7 +204,7 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle) { GdkFrameClockIdlePrivate *priv = clock_idle->priv; - if (priv->freeze_count == 0) + if (priv->freeze_count == 0 && priv->requested != 0) { guint min_interval = 0; @@ -240,6 +240,23 @@ maybe_start_idle (GdkFrameClockIdle *clock_idle) } } +static gint64 +compute_min_next_frame_time (GdkFrameClockIdle *clock_idle, + gint64 last_frame_time) +{ + gint64 presentation_time; + gint64 refresh_interval; + + gdk_frame_clock_get_refresh_info (GDK_FRAME_CLOCK (clock_idle), + last_frame_time, + &refresh_interval, &presentation_time); + + if (presentation_time == 0) + return last_frame_time + refresh_interval; + else + return presentation_time + refresh_interval / 2; +} + static gboolean gdk_frame_clock_flush_idle (void *data) { @@ -277,6 +294,7 @@ gdk_frame_clock_paint_idle (void *data) priv->paint_idle_id = 0; priv->in_paint_idle = TRUE; + priv->min_next_frame_time = 0; skip_to_resume_events = (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0; @@ -403,19 +421,16 @@ gdk_frame_clock_paint_idle (void *data) priv->in_paint_idle = FALSE; - if (priv->freeze_count == 0 && priv->requested != 0) + /* If there is throttling in the backend layer, then we'll do another + * update as soon as the backend unthrottles (if there is work to do), + * otherwise we need to figure when the next frame should be. + */ + if (priv->freeze_count == 0) { - /* We need to start over again immediately - this implies that there is no - * throttling at the backend layer, so we need to back-off ourselves. - */ - gdk_flush (); - priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL; + priv->min_next_frame_time = compute_min_next_frame_time (clock_idle, + priv->frame_time); maybe_start_idle (clock_idle); } - else - { - priv->min_next_frame_time = 0; - } if (priv->freeze_count == 0) priv->sleep_serial = get_sleep_serial (); diff --git a/gdk/gdkframehistory.c b/gdk/gdkframehistory.c index 322467bc86..2f7147ef73 100644 --- a/gdk/gdkframehistory.c +++ b/gdk/gdkframehistory.c @@ -154,6 +154,7 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history, gint64 frame_end_time = _gdk_frame_timings_get_frame_end_time (timings); gint64 frame_time = gdk_frame_timings_get_frame_time (timings); gint64 presentation_time = gdk_frame_timings_get_presentation_time (timings); + gint64 predicted_presentation_time = gdk_frame_timings_get_predicted_presentation_time (timings); gint64 refresh_interval = gdk_frame_timings_get_refresh_interval (timings); gint64 previous_frame_time = 0; gboolean slept_before = gdk_frame_timings_get_slept_before (timings); @@ -177,6 +178,8 @@ _gdk_frame_history_debug_print (GdkFrameHistory *history, g_print (" frame_end=%-4.1f", (frame_end_time - frame_time) / 1000.); if (presentation_time != 0) g_print (" present=%-4.1f", (presentation_time - frame_time) / 1000.); + if (predicted_presentation_time != 0) + g_print (" predicted=%-4.1f", (predicted_presentation_time - frame_time) / 1000.); if (refresh_interval != 0) g_print (" refresh_interval=%-4.1f", refresh_interval / 1000.); g_print ("\n"); diff --git a/gdk/gdkframetimings.c b/gdk/gdkframetimings.c index 08130015fd..ad9ec2e527 100644 --- a/gdk/gdkframetimings.c +++ b/gdk/gdkframetimings.c @@ -29,6 +29,7 @@ struct _GdkFrameTimings gint64 drawn_time; gint64 presentation_time; gint64 refresh_interval; + gint64 predicted_presentation_time; #ifdef G_ENABLE_DEBUG gint64 layout_start_time; @@ -187,6 +188,23 @@ gdk_frame_timings_set_presentation_time (GdkFrameTimings *timings, timings->presentation_time = presentation_time; } +gint64 +gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings) +{ + g_return_val_if_fail (timings != NULL, 0); + + return timings->predicted_presentation_time; +} + +void +gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings, + gint64 predicted_presentation_time) +{ + g_return_if_fail (timings != NULL); + + timings->predicted_presentation_time = predicted_presentation_time; +} + gint64 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timings) { diff --git a/gdk/gdkframetimings.h b/gdk/gdkframetimings.h index 53dbffbd06..8e86c6e69f 100644 --- a/gdk/gdkframetimings.h +++ b/gdk/gdkframetimings.h @@ -62,6 +62,10 @@ gint64 gdk_frame_timings_get_refresh_interval (GdkFrameTimings *timin void gdk_frame_timings_set_refresh_interval (GdkFrameTimings *timings, gint64 refresh_interval); +gint64 gdk_frame_timings_get_predicted_presentation_time (GdkFrameTimings *timings); +void gdk_frame_timings_set_predicted_presentation_time (GdkFrameTimings *timings, + gint64 predicted_presentation_time); + G_END_DECLS #endif /* __GDK_FRAME_TIMINGS_H__ */ diff --git a/gdk/x11/gdkdisplay-x11.c b/gdk/x11/gdkdisplay-x11.c index 888d87dd70..cf26b3016d 100644 --- a/gdk/x11/gdkdisplay-x11.c +++ b/gdk/x11/gdkdisplay-x11.c @@ -1108,18 +1108,27 @@ _gdk_wm_protocols_filter (GdkXEvent *xev, guint32 d3 = xevent->xclient.data.l[3]; guint64 serial = ((guint64)d0 << 32) | d1; + gint64 frame_drawn_time = ((guint64)d2 << 32) | d3; + gint64 refresh_interval, presentation_time; GdkFrameClock *clock = gdk_window_get_frame_clock (event->any.window); GdkFrameTimings *timings = find_frame_timings (clock, serial); if (timings) - gdk_frame_timings_set_drawn_time (timings, ((guint64)d2 << 32) | d3); + gdk_frame_timings_set_drawn_time (timings, frame_drawn_time); if (window_impl->toplevel->frame_pending) { window_impl->toplevel->frame_pending = FALSE; gdk_frame_clock_thaw (clock); } + + gdk_frame_clock_get_refresh_info (clock, + frame_drawn_time, + &refresh_interval, + &presentation_time); + if (presentation_time != 0) + window_impl->toplevel->throttled_presentation_time = presentation_time + refresh_interval; } return GDK_FILTER_REMOVE; diff --git a/gdk/x11/gdkwindow-x11.c b/gdk/x11/gdkwindow-x11.c index ea74baa3f4..d3fac4df5f 100644 --- a/gdk/x11/gdkwindow-x11.c +++ b/gdk/x11/gdkwindow-x11.c @@ -277,6 +277,58 @@ unhook_surface_changed (GdkWindow *window) NULL, NULL); } +static void +gdk_x11_window_predict_presentation_time (GdkWindow *window) +{ + GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl); + GdkFrameClock *clock; + GdkFrameTimings *timings; + gint64 frame_time; + gint64 presentation_time; + gint64 refresh_interval; + gboolean slept_before; + + if (!WINDOW_IS_TOPLEVEL (window)) + return; + + clock = gdk_window_get_frame_clock (window); + + timings = gdk_frame_clock_get_current_frame_timings (clock); + frame_time = gdk_frame_timings_get_frame_time (timings); + slept_before = gdk_frame_timings_get_slept_before (timings); + + gdk_frame_clock_get_refresh_info (clock, + frame_time, + &refresh_interval, &presentation_time); + + if (presentation_time != 0) + { + if (slept_before) + { + presentation_time += refresh_interval; + } + else + { + if (presentation_time < frame_time + refresh_interval / 2) + presentation_time += refresh_interval; + } + } + else + { + if (slept_before) + presentation_time = frame_time + refresh_interval + refresh_interval / 2; + else + presentation_time = frame_time + refresh_interval; + } + + if (presentation_time < impl->toplevel->throttled_presentation_time) + presentation_time = impl->toplevel->throttled_presentation_time; + + gdk_frame_timings_set_predicted_presentation_time (timings, + presentation_time); + +} + static void gdk_x11_window_begin_frame (GdkWindow *window) { @@ -299,8 +351,6 @@ static void gdk_x11_window_end_frame (GdkWindow *window) { GdkFrameClock *clock; - GdkFrameHistory *history; - gint64 frame_counter; GdkFrameTimings *timings; GdkWindowImplX11 *impl; @@ -314,9 +364,7 @@ gdk_x11_window_end_frame (GdkWindow *window) return; clock = gdk_window_get_frame_clock (window); - history = gdk_frame_clock_get_history (clock); - frame_counter = gdk_frame_history_get_frame_counter (history); - timings = gdk_frame_history_get_timings (history, frame_counter); + timings = gdk_frame_clock_get_current_frame_timings (clock); impl->toplevel->in_frame = FALSE; @@ -880,6 +928,7 @@ static void on_frame_clock_before_paint (GdkFrameClock *clock, GdkWindow *window) { + gdk_x11_window_predict_presentation_time (window); gdk_x11_window_begin_frame (window); } diff --git a/gdk/x11/gdkwindow-x11.h b/gdk/x11/gdkwindow-x11.h index 8dde335011..7eb95a07f2 100644 --- a/gdk/x11/gdkwindow-x11.h +++ b/gdk/x11/gdkwindow-x11.h @@ -156,6 +156,10 @@ struct _GdkToplevelX11 * ConfigureNotify */ gint64 current_counter_value; + + /* After a _NET_WM_FRAME_DRAWN message, this is the soonest that we think + * frame after will be presented */ + gint64 throttled_presentation_time; #endif };