]> Pileus Git - ~andy/gtk/blobdiff - gdk/gdkframeclock.c
stylecontext: Do invalidation on first resize container
[~andy/gtk] / gdk / gdkframeclock.c
index ca5484080d52a1a19a34fc427371b8145480b258..29dd5a281b94e5d80bc11cba2f0f6ed62b14cb5d 100644 (file)
 
 #include "config.h"
 
-#include "gdkframeclock.h"
+#include "gdkframeclockprivate.h"
+#include "gdkinternals.h"
 
 /**
- * SECTION:frameclock
+ * SECTION:gdkframeclock
  * @Short_description: Frame clock syncs painting to a window or display
  * @Title: Frame clock
  *
- * A #GdkFrameClock tells the application when to repaint a window.
- * This may be synced to the vertical refresh rate of the monitor, for
- * example. Even when the frame clock uses a simple timer rather than
- * a hardware-based vertical sync, the frame clock helps because it
- * ensures everything paints at the same time (reducing the total
- * number of frames). The frame clock can also automatically stop
- * painting when it knows the frames will not be visible, or scale back
- * animation framerates.
+ * A #GdkFrameClock tells the application when to update and repaint a
+ * window. This may be synced to the vertical refresh rate of the
+ * monitor, for example. Even when the frame clock uses a simple timer
+ * rather than a hardware-based vertical sync, the frame clock helps
+ * because it ensures everything paints at the same time (reducing the
+ * total number of frames). The frame clock can also automatically
+ * stop painting when it knows the frames will not be visible, or
+ * scale back animation framerates.
  *
  * #GdkFrameClock is designed to be compatible with an OpenGL-based
  * implementation or with mozRequestAnimationFrame in Firefox,
  * for example.
  *
  * A frame clock is idle until someone requests a frame with
- * gdk_frame_clock_request_frame(). At that time, the frame clock
- * emits its GdkFrameClock:frame-requested signal if no frame was
- * already pending.
+ * gdk_frame_clock_request_phase(). At some later point that makes
+ * sense for the synchronization being implemented, the clock will
+ * process a frame and emit signals for each phase that has been
+ * requested. (See the signals of the #GdkFrameClock class for
+ * documentation of the phases. %GDK_FRAME_CLOCK_PHASE_UPDATE and the
+ * #GdkFrameClock::update signal are most interesting for application
+ * writers, and are used to update the animations, using the frame time
+ * given by gdk_frame_clock_get_frame_time().
  *
- * At some later time after the frame is requested, the frame clock
- * MAY indicate that a frame should be painted. To paint a frame the
- * clock will: Emit GdkFrameClock:before-paint; update the frame time
- * in the default handler for GdkFrameClock:before-paint; emit
- * GdkFrameClock:paint; emit GdkFrameClock:after-paint.  The app
- * should paint in a handler for the paint signal.
- *
- * If a given frame is not painted (the clock is idle), the frame time
- * should still update to a conceptual "last frame." i.e. the frame
- * time will keep moving forward roughly with wall clock time.
- *
- * The frame time is in milliseconds. However, it should not be
- * thought of as having any particular relationship to wall clock
- * time. Unlike wall clock time, it "snaps" to conceptual frame times
- * so is low-resolution; it is guaranteed to never move backward (so
- * say you reset your computer clock, the frame clock will not reset);
- * and the frame clock is allowed to drift. For example nicer
- * results when painting with vertical refresh sync may be obtained by
- * painting as rapidly as possible, but always incrementing the frame
- * time by the frame length on each frame. This results in a frame
- * time that doesn't have a lot to do with wall clock time.
+ * The frame time is reported in microseconds and generally in the same
+ * timescale as g_get_monotonic_time(), however, it is not the same
+ * as g_get_monotonic_time(). The frame time does not advance during
+ * the time a frame is being painted, and outside of a frame, an attempt
+ * is made so that all calls to gdk_frame_clock_get_frame_time() that
+ * are called at a "similar" time get the same value. This means that
+ * if different animations are timed by looking at the difference in
+ * time between an initial value from gdk_frame_clock_get_frame_time()
+ * and the value inside the #GdkFrameClock::update signal of the clock,
+ * they will stay exactly synchronized.
  */
 
-G_DEFINE_INTERFACE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT)
+G_DEFINE_ABSTRACT_TYPE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT)
 
 enum {
-  FRAME_REQUESTED,
+  FLUSH_EVENTS,
   BEFORE_PAINT,
+  UPDATE,
   LAYOUT,
   PAINT,
   AFTER_PAINT,
+  RESUME_EVENTS,
   LAST_SIGNAL
 };
 
 static guint signals[LAST_SIGNAL];
 
+#define FRAME_HISTORY_MAX_LENGTH 16
+
+struct _GdkFrameClockPrivate
+{
+  gint64 frame_counter;
+  gint n_timings;
+  gint current;
+  GdkFrameTimings *timings[FRAME_HISTORY_MAX_LENGTH];
+};
+
 static void
-gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
+gdk_frame_clock_finalize (GObject *object)
 {
+  GdkFrameClockPrivate *priv = GDK_FRAME_CLOCK (object)->priv;
+  int i;
+
+  for (i = 0; i < FRAME_HISTORY_MAX_LENGTH; i++)
+    if (priv->timings[i] != 0)
+      gdk_frame_timings_unref (priv->timings[i]);
+
+  G_OBJECT_CLASS (gdk_frame_clock_parent_class)->finalize (object);
+}
+
+static void
+gdk_frame_clock_class_init (GdkFrameClockClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = gdk_frame_clock_finalize;
+
   /**
-   * GdkFrameClock::frame-requested:
+   * GdkFrameClock::flush-events:
    * @clock: the frame clock emitting the signal
    *
-   * This signal is emitted when a frame is not pending, and
-   * gdk_frame_clock_request_frame() is called to request a frame.
+   * This signal is used to flush pending motion events that
+   * are being batched up and compressed together. Applications
+   * should not handle this signal.
    */
-  signals[FRAME_REQUESTED] =
-    g_signal_new (g_intern_static_string ("frame-requested"),
+  signals[FLUSH_EVENTS] =
+    g_signal_new (g_intern_static_string ("flush-events"),
                   GDK_TYPE_FRAME_CLOCK,
                   G_SIGNAL_RUN_LAST,
                   0,
@@ -110,9 +135,8 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
    * GdkFrameClock::before-paint:
    * @clock: the frame clock emitting the signal
    *
-   * This signal is emitted immediately before the paint signal and
-   * indicates that the frame time has been updated, and signal
-   * handlers should perform any preparatory work before painting.
+   * This signal begins processing of the frame. Applications
+   * should generally not handle this signal.
    */
   signals[BEFORE_PAINT] =
     g_signal_new (g_intern_static_string ("before-paint"),
@@ -123,13 +147,34 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
                   g_cclosure_marshal_VOID__VOID,
                   G_TYPE_NONE, 0);
 
+  /**
+   * GdkFrameClock::update:
+   * @clock: the frame clock emitting the signal
+   *
+   * This signal is emitted as the first step of toolkit and
+   * application processing of the frame. Animations should
+   * be updated using gdk_frame_clock_get_frame_time().
+   * Applications can connect directly to this signal, or
+   * use gtk_widget_add_tick_callback() as a more convenient
+   * interface.
+   */
+  signals[UPDATE] =
+    g_signal_new (g_intern_static_string ("update"),
+                  GDK_TYPE_FRAME_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
   /**
    * GdkFrameClock::layout:
    * @clock: the frame clock emitting the signal
    *
-   * This signal is emitted immediately before the paint signal and
-   * indicates that the frame time has been updated, and signal
-   * handlers should perform any preparatory work before painting.
+   * This signal is emitted as the second step of toolkit and
+   * application processing of the frame. Any work to update
+   * sizes and positions of application elements should be
+   * performed. GTK+ normally handles this internally.
    */
   signals[LAYOUT] =
     g_signal_new (g_intern_static_string ("layout"),
@@ -144,8 +189,11 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
    * GdkFrameClock::paint:
    * @clock: the frame clock emitting the signal
    *
-   * Signal handlers for this signal should paint the window, screen,
-   * or whatever they normally paint.
+   * This signal is emitted as the third step of toolkit and
+   * application processing of the frame. The frame is
+   * repainted. GDK normally handles this internally and
+   * produces expose events, which are turned into GTK+
+   * #GtkWidget::draw signals.
    */
   signals[PAINT] =
     g_signal_new (g_intern_static_string ("paint"),
@@ -160,10 +208,8 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
    * GdkFrameClock::after-paint:
    * @clock: the frame clock emitting the signal
    *
-   * This signal is emitted immediately after the paint signal and
-   * allows signal handlers to do anything they'd like to do after
-   * painting has been completed. This is a relatively good time to do
-   * "expensive" processing in order to get it done in between frames.
+   * This signal ends processing of the frame. Applications
+   * should generally not handle this signal.
    */
   signals[AFTER_PAINT] =
     g_signal_new (g_intern_static_string ("after-paint"),
@@ -173,136 +219,388 @@ gdk_frame_clock_default_init (GdkFrameClockInterface *iface)
                   NULL, NULL,
                   g_cclosure_marshal_VOID__VOID,
                   G_TYPE_NONE, 0);
+
+  /**
+   * GdkFrameClock::resume-events:
+   * @clock: the frame clock emitting the signal
+   *
+   * This signal is emitted after processing of the frame is
+   * finished, and is handled internally by GTK+ to resume normal
+   * event processing. Applications should not handle this signal.
+   */
+  signals[RESUME_EVENTS] =
+    g_signal_new (g_intern_static_string ("resume-events"),
+                  GDK_TYPE_FRAME_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  g_type_class_add_private (klass, sizeof (GdkFrameClockPrivate));
+}
+
+static void
+gdk_frame_clock_init (GdkFrameClock *clock)
+{
+  GdkFrameClockPrivate *priv;
+
+  clock->priv = G_TYPE_INSTANCE_GET_PRIVATE (clock,
+                                             GDK_TYPE_FRAME_CLOCK,
+                                             GdkFrameClockPrivate);
+  priv = clock->priv;
+
+  priv->frame_counter = -1;
+  priv->current = FRAME_HISTORY_MAX_LENGTH - 1;
 }
 
 /**
  * gdk_frame_clock_get_frame_time:
- * @clock: the clock
+ * @frame_clock: a #GdkFrameClock
  *
  * Gets the time that should currently be used for animations.  Inside
- * a paint, it's the time used to compute the animation position of
- * everything in a frame. Outside a paint, it's the time of the
- * conceptual "previous frame," which may be either the actual
- * previous frame time, or if that's too old, an updated time.
+ * the processing of a frame, it's the time used to compute the
+ * animation position of everything in a frame. Outside of a frame, it's
+ * the time of the conceptual "previous frame," which may be either
+ * the actual previous frame time, or if that's too old, an updated
+ * time.
  *
- * The returned time has no relationship to wall clock time.  It
- * increases roughly at 1 millisecond per wall clock millisecond, and
- * it never decreases, but its value is only meaningful relative to
- * previous frame clock times.
+ * Since: 3.8
+ * Return value: a timestamp in microseconds, in the timescale of
+ *  of g_get_monotonic_time().
+ */
+gint64
+gdk_frame_clock_get_frame_time (GdkFrameClock *frame_clock)
+{
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
+
+  return GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->get_frame_time (frame_clock);
+}
+
+/**
+ * gdk_frame_clock_request_phase:
+ * @frame_clock: a #GdkFrameClock
+ * @phase: the phase that is requested
  *
+ * Asks the frame clock to run a particular phase. The signal
+ * corresponding the requested phase will be emitted the next
+ * time the frame clock processes. Multiple calls to
+ * gdk_frame_clock_request_phase() will be combined together
+ * and only one frame processed. If you are displaying animated
+ * content and want to continually request the
+ * %GDK_FRAME_CLOCK_PHASE_UPDATE phase for a period of time,
+ * you should use gdk_frame_clock_begin_updating() instead, since
+ * this allows GTK+ to adjust system parameters to get maximally
+ * smooth animations.
  *
- * Since: 3.0
- * Return value: a timestamp in milliseconds
+ * Since: 3.8
  */
-guint64
-gdk_frame_clock_get_frame_time (GdkFrameClock *clock)
+void
+gdk_frame_clock_request_phase (GdkFrameClock      *frame_clock,
+                               GdkFrameClockPhase  phase)
 {
-  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
 
-  return GDK_FRAME_CLOCK_GET_IFACE (clock)->get_frame_time (clock);
+  GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->request_phase (frame_clock, phase);
 }
 
 /**
- * gdk_frame_clock_request_frame:
- * @clock: the clock
+ * gdk_frame_clock_begin_updating:
+ * @frame_clock: a #GdkFrameClock
  *
- * Asks the frame clock to paint a frame. The frame
- * may or may not ever be painted (the frame clock may
- * stop itself for whatever reason), but the goal in
- * normal circumstances would be to paint the frame
- * at the next expected frame time. For example
- * if the clock is running at 60fps the frame would
- * ideally be painted within 1000/60=16 milliseconds.
+ * Starts updates for an animation. Until a matching call to
+ * gdk_frame_clock_end_updating() is made, the frame clock will continually
+ * request a new frame with the %GDK_FRAME_CLOCK_PHASE_UPDATE phase.
+ * This function may be called multiple times and frames will be
+ * requested until gdk_frame_clock_end_updating() is called the same
+ * number of times.
  *
- * Since: 3.0
+ * Since: 3.8
  */
 void
-gdk_frame_clock_request_frame (GdkFrameClock *clock)
+gdk_frame_clock_begin_updating (GdkFrameClock *frame_clock)
 {
-  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
 
-  GDK_FRAME_CLOCK_GET_IFACE (clock)->request_frame (clock);
+  GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->begin_updating (frame_clock);
 }
 
 /**
- * gdk_frame_clock_get_frame_requested:
- * @clock: the clock
+ * gdk_frame_clock_end_updating:
+ * @frame_clock: a #GdkFrameClock
+ *
+ * Stops updates for an animation. See the documentation for
+ * gdk_frame_clock_begin_updating().
  *
- * Gets whether a frame paint has been requested but has not been
- * performed.
+ * Since: 3.8
+ */
+void
+gdk_frame_clock_end_updating (GdkFrameClock *frame_clock)
+{
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
+
+  GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->end_updating (frame_clock);
+}
+
+void
+_gdk_frame_clock_freeze (GdkFrameClock *clock)
+{
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+
+  GDK_FRAME_CLOCK_GET_CLASS (clock)->freeze (clock);
+}
+
+
+void
+_gdk_frame_clock_thaw (GdkFrameClock *clock)
+{
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+
+  GDK_FRAME_CLOCK_GET_CLASS (clock)->thaw (clock);
+}
+
+/**
+ * gdk_frame_clock_get_frame_counter:
+ * @frame_clock: a #GdkFrameClock
  *
+ * A #GdkFrameClock maintains a 64-bit counter that increments for
+ * each frame drawn.
  *
- * Since: 3.0
- * Return value: TRUE if a frame paint is pending
+ * Returns: inside frame processing, the value of the frame counter
+ *  for the current frame. Outside of frame processing, the frame
+ *   counter for the last frame.
+ * Since: 3.8
  */
-gboolean
-gdk_frame_clock_get_frame_requested (GdkFrameClock *clock)
+gint64
+gdk_frame_clock_get_frame_counter (GdkFrameClock *frame_clock)
 {
-  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), FALSE);
+  GdkFrameClockPrivate *priv;
+
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
+
+  priv = frame_clock->priv;
 
-  return GDK_FRAME_CLOCK_GET_IFACE (clock)->get_frame_requested (clock);
+  return priv->frame_counter;
 }
 
 /**
- * gdk_frame_clock_get_frame_time_val:
- * @clock: the clock
- * @timeval: #GTimeVal to fill in with frame time
+ * gdk_frame_clock_get_history_start:
+ * @frame_clock: a #GdkFrameClock
  *
- * Like gdk_frame_clock_get_frame_time() but returns the time as a
- * #GTimeVal which may be handy with some APIs (such as
- * #GdkPixbufAnimation).
+ * #GdkFrameClock internally keeps a history of #GdkFrameTiming
+ * objects for recent frames that can be retrieved with
+ * gdk_frame_clock_get_timings(). The set of stored frames
+ * is the set from the counter values given by
+ * gdk_frame_clock_get_history_start() and
+ * gdk_frame_clock_get_frame_counter(), inclusive.
+ *
+ * Return value: the frame counter value for the oldest frame
+ *  that is available in the internal frame history of the
+ *  #GdkFrameClock.
+ * Since: 3.8
  */
+gint64
+gdk_frame_clock_get_history_start (GdkFrameClock *frame_clock)
+{
+  GdkFrameClockPrivate *priv;
+
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
+
+  priv = frame_clock->priv;
+
+  return priv->frame_counter + 1 - priv->n_timings;
+}
+
 void
-gdk_frame_clock_get_frame_time_val (GdkFrameClock *clock,
-                                    GTimeVal      *timeval)
+_gdk_frame_clock_begin_frame (GdkFrameClock *frame_clock)
 {
-  guint64 time_ms;
+  GdkFrameClockPrivate *priv;
 
-  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
 
-  time_ms = gdk_frame_clock_get_frame_time (clock);
+  priv = frame_clock->priv;
 
-  timeval->tv_sec = time_ms / 1000;
-  timeval->tv_usec = (time_ms % 1000) * 1000;
+  priv->frame_counter++;
+  priv->current = (priv->current + 1) % FRAME_HISTORY_MAX_LENGTH;
+
+  if (priv->n_timings < FRAME_HISTORY_MAX_LENGTH)
+    priv->n_timings++;
+  else
+    {
+      gdk_frame_timings_unref(priv->timings[priv->current]);
+    }
+
+  priv->timings[priv->current] = _gdk_frame_timings_new (priv->frame_counter);
 }
 
 /**
- * gdk_frame_clock_frame_requested:
- * @clock: the clock
+ * gdk_frame_clock_get_timings:
+ * @frame_clock: a #GdkFrameClock
+ * @frame_counter: the frame counter value identifying the frame to
+ *  be received.
  *
- * Emits the frame-requested signal. Used in implementations of the
- * #GdkFrameClock interface.
+ * Retrieves a #GdkFrameTimings object holding timing information
+ * for the current frame or a recent frame. The #GdkFrameTimings
+ * object may not yet be complete: see gdk_frame_timings_get_complete().
+ *
+ * Return value: the #GdkFrameTimings object for the specified
+ *  frame, or %NULL if it is not available. See
+ *  gdk_frame_clock_get_history_start().
+ * Since: 3.8
  */
+GdkFrameTimings *
+gdk_frame_clock_get_timings (GdkFrameClock *frame_clock,
+                             gint64         frame_counter)
+{
+  GdkFrameClockPrivate *priv;
+  gint pos;
+
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), NULL);
+
+  priv = frame_clock->priv;
+
+  if (frame_counter > priv->frame_counter)
+    return NULL;
+
+  if (frame_counter <= priv->frame_counter - priv->n_timings)
+    return NULL;
+
+  pos = (priv->current - (priv->frame_counter - frame_counter) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
+
+  return priv->timings[pos];
+}
+
+/**
+ * gdk_frame_clock_get_current_timings:
+ * @frame_clock: a #GdkFrameClock
+ *
+ * Gets the frame timings for the current frame.
+ *
+ * Returns: the #GdkFrameTimings for the frame currently being
+ *  processed, or even no frame is being processed, for the
+ *  previous frame. Before any frames have been procesed,
+ *  returns %NULL.
+ * Since: 3.8
+ */
+GdkFrameTimings *
+gdk_frame_clock_get_current_timings (GdkFrameClock *frame_clock)
+{
+  GdkFrameClockPrivate *priv;
+
+  g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
+
+  priv = frame_clock->priv;
+
+  return gdk_frame_clock_get_timings (frame_clock, priv->frame_counter);
+}
+
+
+#ifdef G_ENABLE_DEBUG
 void
-gdk_frame_clock_frame_requested (GdkFrameClock *clock)
+_gdk_frame_clock_debug_print_timings (GdkFrameClock   *clock,
+                                      GdkFrameTimings *timings)
 {
-  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+  gint64 previous_frame_time = 0;
+  GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (clock,
+                                                                   timings->frame_counter - 1);
 
-  g_signal_emit (G_OBJECT (clock),
-                 signals[FRAME_REQUESTED], 0);
+  if (previous_timings != NULL)
+    previous_frame_time = previous_timings->frame_time;
+
+  g_print ("%5" G_GINT64_FORMAT ":", timings->frame_counter);
+  if (previous_frame_time != 0)
+    {
+      g_print (" interval=%-4.1f", (timings->frame_time - previous_frame_time) / 1000.);
+      g_print (timings->slept_before ?  " (sleep)" : "        ");
+    }
+  if (timings->layout_start_time != 0)
+    g_print (" layout_start=%-4.1f", (timings->layout_start_time - timings->frame_time) / 1000.);
+  if (timings->paint_start_time != 0)
+    g_print (" paint_start=%-4.1f", (timings->paint_start_time - timings->frame_time) / 1000.);
+  if (timings->frame_end_time != 0)
+    g_print (" frame_end=%-4.1f", (timings->frame_end_time - timings->frame_time) / 1000.);
+  if (timings->presentation_time != 0)
+    g_print (" present=%-4.1f", (timings->presentation_time - timings->frame_time) / 1000.);
+  if (timings->predicted_presentation_time != 0)
+    g_print (" predicted=%-4.1f", (timings->predicted_presentation_time - timings->frame_time) / 1000.);
+  if (timings->refresh_interval != 0)
+    g_print (" refresh_interval=%-4.1f", timings->refresh_interval / 1000.);
+  g_print ("\n");
 }
+#endif /* G_ENABLE_DEBUG */
+
+#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
+#define MAX_HISTORY_AGE 150000         /* 150ms */
 
 /**
- * gdk_frame_clock_paint:
- * @clock: the clock
+ * gdk_frame_clock_get_refresh_info:
+ * @frame_clock: a #GdkFrameClock
+ * @base_time: base time for determining a presentaton time
+ * @refresh_interval_return: a location to store the determined refresh
+ *  interval, or %NULL. A default refresh interval of 1/60th of
+ *  a second will be stored if no history is present.
+ * @presentation_time_return: a location to store the next
+ *  candidate presentation time after the given base time.
+ *  0 will be will be stored if no history is present.
  *
- * Emits the before-paint, paint, and after-paint signals. Used in
- * implementations of the #GdkFrameClock interface.
+ * Using the frame history stored in the frame clock, finds the last
+ * known presentation time and refresh interval, and assuming that
+ * presentation times are separated by the refresh interval,
+ * predicts a presentation time that is a multiple of the refresh
+ * interval after the last presentation time, and later than @base_time.
+ *
+ * Since: 3.8
  */
 void
-gdk_frame_clock_paint (GdkFrameClock *clock)
+gdk_frame_clock_get_refresh_info (GdkFrameClock *frame_clock,
+                                  gint64         base_time,
+                                  gint64        *refresh_interval_return,
+                                  gint64        *presentation_time_return)
 {
-  g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
+  gint64 frame_counter;
+
+  g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
+
+  frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
+
+  if (presentation_time_return)
+    *presentation_time_return = 0;
+  if (refresh_interval_return)
+    *refresh_interval_return = DEFAULT_REFRESH_INTERVAL;
+
+  while (TRUE)
+    {
+      GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock, frame_counter);
+      gint64 presentation_time;
+      gint64 refresh_interval;
+
+      if (timings == NULL)
+        return;
+
+      refresh_interval = timings->refresh_interval;
+      presentation_time = timings->presentation_time;
+
+      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;
 
-  g_signal_emit (G_OBJECT (clock),
-                 signals[BEFORE_PAINT], 0);
+              while (presentation_time < base_time)
+                presentation_time += refresh_interval;
 
-  g_signal_emit (G_OBJECT (clock),
-                 signals[LAYOUT], 0);
+              if (presentation_time_return)
+                *presentation_time_return = presentation_time;
+            }
 
-  g_signal_emit (G_OBJECT (clock),
-                 signals[PAINT], 0);
+          return;
+        }
 
-  g_signal_emit (G_OBJECT (clock),
-                 signals[AFTER_PAINT], 0);
+      frame_counter--;
+    }
 }