/* GDK - The GIMP Drawing Kit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /* * Modified by the GTK+ Team and others 1997-2010. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" #include "gdkframeclockprivate.h" #include "gdkinternals.h" /** * SECTION:frameclock * @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. * * #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_phase(). At that time, the frame clock * emits its GdkFrameClock:frame-requested signal if no frame was * already pending. * * 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. */ G_DEFINE_ABSTRACT_TYPE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT) enum { 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_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::flush-events: * @clock: the frame clock emitting the signal * * FIXME. */ signals[FLUSH_EVENTS] = g_signal_new (g_intern_static_string ("flush-events"), GDK_TYPE_FRAME_CLOCK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * 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. */ signals[BEFORE_PAINT] = g_signal_new (g_intern_static_string ("before-paint"), GDK_TYPE_FRAME_CLOCK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GdkFrameClock::update: * @clock: the frame clock emitting the signal * * FIXME. */ 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. */ signals[LAYOUT] = g_signal_new (g_intern_static_string ("layout"), GDK_TYPE_FRAME_CLOCK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GdkFrameClock::paint: * @clock: the frame clock emitting the signal * * Signal handlers for this signal should paint the window, screen, * or whatever they normally paint. */ signals[PAINT] = g_signal_new (g_intern_static_string ("paint"), GDK_TYPE_FRAME_CLOCK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * 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. */ signals[AFTER_PAINT] = g_signal_new (g_intern_static_string ("after-paint"), GDK_TYPE_FRAME_CLOCK, G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); /** * GdkFrameClock::resume-events: * @clock: the frame clock emitting the signal * * FIXME. */ 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 * * 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 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.0 * Return value: a timestamp in milliseconds */ guint64 gdk_frame_clock_get_frame_time (GdkFrameClock *clock) { g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0); return GDK_FRAME_CLOCK_GET_CLASS (clock)->get_frame_time (clock); } /** * gdk_frame_clock_request_phase: * @clock: the clock * * 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. * * Since: 3.0 */ void gdk_frame_clock_request_phase (GdkFrameClock *clock, GdkFrameClockPhase phase) { g_return_if_fail (GDK_IS_FRAME_CLOCK (clock)); GDK_FRAME_CLOCK_GET_CLASS (clock)->request_phase (clock, phase); } 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); } gint64 gdk_frame_clock_get_frame_counter (GdkFrameClock *clock) { GdkFrameClockPrivate *priv; g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0); priv = clock->priv; return priv->frame_counter; } gint64 gdk_frame_clock_get_history_start (GdkFrameClock *clock) { GdkFrameClockPrivate *priv; g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0); priv = clock->priv; return priv->frame_counter + 1 - priv->n_timings; } void _gdk_frame_clock_begin_frame (GdkFrameClock *clock) { GdkFrameClockPrivate *priv; g_return_if_fail (GDK_IS_FRAME_CLOCK (clock)); priv = clock->priv; 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); } GdkFrameTimings * gdk_frame_clock_get_timings (GdkFrameClock *clock, gint64 frame_counter) { GdkFrameClockPrivate *priv; gint pos; g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), NULL); priv = 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]; } GdkFrameTimings * gdk_frame_clock_get_frame_timings (GdkFrameClock *clock) { GdkFrameClockPrivate *priv; g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0); priv = clock->priv; return gdk_frame_clock_get_timings (clock, priv->frame_counter); } #ifdef G_ENABLE_DEBUG void _gdk_frame_clock_debug_print_timings (GdkFrameClock *clock, GdkFrameTimings *timings) { gint64 previous_frame_time = 0; GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (clock, timings->frame_counter - 1); 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 */ void gdk_frame_clock_get_refresh_info (GdkFrameClock *clock, gint64 base_time, gint64 *refresh_interval_return, gint64 *presentation_time_return) { gint64 frame_counter; g_return_if_fail (GDK_IS_FRAME_CLOCK (clock)); frame_counter = gdk_frame_clock_get_frame_counter (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 (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; while (presentation_time < base_time) presentation_time += refresh_interval; if (presentation_time_return) *presentation_time_return = presentation_time; } return; } frame_counter--; } }