paint clock



Hi,

I did some doodling around to figure out how GTK could have a "master
clock" or "paint clock." Some of the problems are a little thorny and
I need to put this down for a while so I thought I'd throw out some
work in progress for discussion.
Unfinished patch attached just to show the direction I was going in,
it's not intended to be working or even compile.

Current GTK+ Model
===

GTK+ right now paints in an idle; painting in an idle defers paint
until after all events have been processed and the request/alloc
resize idle has run.

However, painting in an idle fails and looks bad if you are trying to
do animation or processing a lot of user interaction. If you grep for
"process_updates" quite a few places in GTK force immediate repaint
because they are scrolling, animating a GtkImage, drawing a
rubberband, or interactively resizing a window.

Painting in an idle is also just plain incompatible with how OpenGL
and graphics hardware ideally work. What you ideally want to do is
paint on every vertical refresh, so it's important to not miss a
refresh while waiting to go idle, and it's important not to paint at
some random forced time prior to a refresh when you could be doing
other work.

Painting in an idle can result in painting too often, not often
enough, or in an irregular/lumpy way.

Switching GTK to a paint clock model might have the following benefits:
* animations could look smooth even without manual forced process_updates
* multiple animations could sync on the "frame time" used to compute
tweens on a given frame
* GTK could more easily be GL-accelerated
* if the master clock were pluggable, GTK could more easily be
integrated into another system's paint clock setup, such as Clutter's,
which would be one way to accomplish the GL acceleration
* GTK could automatically reduce or increase frame rate according to
system capabilities or any other consideration

Alternative to GTK+ Paint Clock
===

Rather than trying to add a paint clock to GTK, a different approach would be:

* add a way to disable GTK ever using the resize/repaint idles for a
given widget hierarchy
* add a way for containers to intercept resize and redraw requests on
their children (queue_resize, queue_draw). Clutter already has such a
way - just make queue_draw invoke a thing on the parent instead of
walking up the widget hierarchy itself.
* in something like clutter-gtk, the resize/repaint idles would be
replaced with clutter's
* the manual process_updates() done by some widgets would also need to
be gotten rid of someway

The upside to this alternative: easier to do, less GTK+ breakage.
The downside: allows fixing GTK+ when embedded in Clutter, but doesn't
allow fixing GTK+ standalone.

Clutter-GTK right now
===

Clutter GTK is currently what I'd consider backwards, i.e. it runs
Clutter paint off the GTK idle. Which downgrades Clutter to GTK's
behavior.
http://git.clutter-project.org/clutter-gtk/tree/clutter-gtk/gtk-clutter-embed.c

In the "Alternative to GTK+ Paint Clock" approach above, GTK+ would
have the same hook as ClutterActor for replacing queue_redraw which
would allow flipping how this works so Clutter's master clock would be
used.

Clutter Model
===

Clutter used to work in a way not unlike GTK, but now it has a "master clock."
http://git.clutter-project.org/clutter/tree/clutter/clutter-master-clock.c

The Clutter master clock can't be lifted wholesale because it isn't
"model view" but rather is an "omniscient controller" that knows about
the stuff that needs to be done per tick. As in the attached WIP
patch, I think the GTK clock should be an interface representing a
model object, among other reasons because then clutter-gtk could
implement that interface with the clutter master clock.

A clutter master clock "iteration" is something like:

1. if syncing to vblank, always paint as fast as possible. Otherwise,
if first paint after sleeping, paint immediately. Otherwise block in
main loop for remaining frame length since last frame start.
   (note: without high-res timers added to kernel ca. 2009, poll()
resolution is 10ms which is half a frame...)

2. Force-dispatch all queued X events.

3. Update tweens (timelines) with the frame's timestamp, to get all
actors in the proper state.

4. Run some application-added "before repaint" hooks

5. Paint and swap buffers

Step 2 is roughly equivalent to making the X event source have a
priority higher than G_PRIORITY_DEFAULT.

Mozilla Model
===

See http://weblogs.mozillazine.org/roc/archives/2010/08/mozrequestanima.html

The API mozilla uses looks like this:

var start = window.mozAnimationStartTime;
function step(event) {
  var progress = event.timeStamp - start;
  d.style.left = Math.min(progress/10, 200) + "px";
  if (progress < 2000) {
    window.mozRequestAnimationFrame();
  } else {
    window.removeEventListener("MozBeforePaint", step, false);
  }
}
window.addEventListener("MozBeforePaint", step, false);
window.mozRequestAnimationFrame();

I don't know how they are actually implementing it though, i.e. I
don't know what they do on each iteration in the guts of their toolkit
code.

litl shell model
===

In the litl shell (source not available sadly) we've experimented with
making things smooth and pretty and fast quite a bit, and landed on a
model where we dropped g_main_loop_run() in favor of manually doing
g_main_context_iteration(). Our main loop iterations are like:

1. Start a timer.

2. Force-dispatch all queued X events up to 5ms of time passed. Stop
dispatching if we reach 5ms.

3. Run one nonblocking main loop iteration (always, even if 5ms passed)

4. While the iteration returns TRUE (more to do) and (the timer from
step 1 is still not up to 5ms OR we don't have a paint queued), do
more nonblocking iterations.

5. If we have any windows using pointer motion hint mask that got a
motion event since last frame, send async QueryPointer requests. Also
send some other async X requests. We have to avoid blocking on X round
trips because with indirect rendering they hose things entirely.

6. Update tweens with frame timestamp

7. Relayout (request/allocate)

8. Block to collect async reply to buffer swap from previous frame
(using indirect rendering here; collecting the "buffer swapped" event
in newer direct rendering stuff would have the same effect)

9. Send async swap buffers request

10. If we need to paint or collect outstanding async requests, goto 1.
If nothing to do, do a blocking main loop iteration which potentially
sleeps.

Another detail of litl shell model is that we always update the frame
time by the frame length, rather than using wall clock time. This
looks much better when just painting as fast as possible with a
nonblocking buffer swap, because you can actually draw three frames in
say 20ms, but they will display over 60ms. So the tween times should
be at 20ms intervals not 7ms intervals. We do drop frames but only if
we get a whole frame behind, and then we still keep the frame times on
multiples of the frame interval.

If we were using g_main_loop_run(), what we're doing in effect is
having the X event source above G_PRIORITY_DEFAULT until 5ms have
passed, then we dump it down to G_PRIORITY_LOW. The paint source is at
G_PRIORITY_LOW until 5ms have passed, and then it pops up above
G_PRIORITY_DEFAULT to run immediately. I'm not sure if we could
convince g_main_loop_run to do this. The idea of course is to try to
keep the frame rate up even if we have a flood of dbus messages or
events or incoming http data or whatever it is.

We have one other weird detail that would be hard to do with
g_main_loop_run() which is that when we do a blocking main loop
iteration, we first do it with a half-second ultra-low-priority
timeout installed. If that thing fires then nothing happened (not even
an idle) for half a second. At that point we run a GC and remove the
timeout, potentially actually sleeping for an indefinite time. This is
very gjs-specific but perhaps illustrates that applications might want
to replace the paint clock as well in order to do weird app-specific
things.

Adding Paint Clock to GTK+
===

The attached patch shows some initial effort to do this. It introduces
a simple paint clock object. and initially implements it with an idle
that works like the current GTK+ idle.

A problem I'm running into is that the paint clock's semantics just
don't really support the current GTK+ API:
* the resize and redraw idle priorities are exposed, and some code
relies on this to basically do before/after resize/paint hooks.
* code does manual process_updates to make animations or scrolling or
resizing look more responsive
* GtkContainer has the whole "resize mode" API

Few widgets rely on this stuff in any serious way, but some code does
rely on it in ways that require at least some fixup.

In my patch the basic approach is to allow setting a paint clock on a
GdkWindow and the idea is that the clock applies to all children of
the window that don't have their own clock set. There's a default
paint clock used for toplevels with no custom paint clock set.

I think the patch needs the paint clock to be a bit more involved; it
needs to be able to control relayout behavior and what happens on a
manual process_updates, perhaps.

Some questions to think about:
* should we do a paint clock at all or just hooks to let containers
reroute queue_draw/resize coming from their children
* should the default GTK+ paint clock remain the idle-based approach
or even without GL should we go to something that's more of a forced
timeline rather than an idle
* should manual process_updates even be allowed? drop/no-op this API?
Or let a window's paint clock decide whether to allow it?
* does it work to have a separate paint clock on a non-native
(client-side) window? I should just try it, from the code it isn't
apparent to me whether painting the parent of a client-side window
will mess up the pixels of the client-side window. i.e. is it required
to repaint a client-side window anytime you paint its parent? If not
should we fix that (clip the client-side window out of parent paint if
it has a different clock) or should we just say that you can't set a
clock on a client-side window?
* should it be possible to override the global default paint clock, or
only per-window?
* should the paint clock replace the resize idle also? (I think yes)

Another note is that I put the clock in GDK, because I think a
Clutter/GDK backend is interesting and right now the paint idle is in
GDK also.

Havoc
diff --git a/gdk/Makefile.am b/gdk/Makefile.am
index 5d651c7..a9fdbd4 100644
--- a/gdk/Makefile.am
+++ b/gdk/Makefile.am
@@ -85,6 +85,7 @@ gdk_public_h_sources =				\
 	gdkkeys.h				\
 	gdkkeysyms.h				\
 	gdkkeysyms-compat.h			\
+	gdkpaintclock.h				\
 	gdkpango.h				\
 	gdkpixbuf.h				\
 	gdkprivate.h				\
@@ -104,6 +105,7 @@ gdk_private_headers =				\
 	gdkinternals.h				\
 	gdkdeviceprivate.h			\
 	gdkintl.h				\
+	gdkpaintclockidle.h			\
 	gdkpoly-generic.h
 
 gdk_c_sources =                 \
@@ -124,6 +126,8 @@ gdk_c_sources =                 \
 	gdkkeys.c		\
 	gdkkeyuni.c		\
 	gdkoffscreenwindow.c	\
+	gdkpaintclock.c		\
+	gdkpaintclockidle.c	\
 	gdkpango.c		\
 	gdkpixbuf-drawable.c	\
 	gdkrectangle.c		\
diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h
index 9f2c4ca..cd2c55a 100644
--- a/gdk/gdkinternals.h
+++ b/gdk/gdkinternals.h
@@ -265,6 +265,8 @@ struct _GdkWindowObject
 
   GList *devices_inside;
   GHashTable *device_events;
+
+  GdkPaintClock *paint_clock; /* NULL to use from parent or default */
 };
 
 #define GDK_WINDOW_TYPE(d) (((GdkWindowObject*)(GDK_WINDOW (d)))->window_type)
diff --git a/gdk/gdkpaintclock.c b/gdk/gdkpaintclock.c
new file mode 100644
index 0000000..c4a0dad
--- /dev/null
+++ b/gdk/gdkpaintclock.c
@@ -0,0 +1,287 @@
+/* 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 "gdkpaintclock.h"
+
+/**
+ * SECTION:paintclock
+ * @Short_description: Paint clock syncs painting to a window or display
+ * @Title: Paint clock
+ *
+ * A #GdkPaintClock 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 paint clock uses a simple timer rather than
+ * a hardware-based vertical sync, the paint clock helps because it
+ * ensures everything paints at the same time (reducing the total
+ * number of frames). The paint clock can also automatically stop
+ * painting when it knows the frames will not be visible, or scale back
+ * animation framerates.
+ *
+ * #GdkPaintClock is designed to be compatible with an OpenGL-based
+ * implementation or with mozRequestAnimationFrame in Firefox,
+ * for example.
+ *
+ * A paint clock is idle until someone requests a frame with
+ * gdk_paint_clock_request_frame(). At that time, the paint clock
+ * emits its GdkPaintClock:frame-requested signal if no frame was
+ * already pending.
+ *
+ * At some later time after the frame is requested, the paint clock
+ * MAY indicate that a frame should be painted. To paint a frame the
+ * clock will: Emit GdkPaintClock:before-paint; update the frame time
+ * in the default handler for GdkPaintClock:before-paint; emit
+ * GdkPaintClock:paint; emit GdkPaintClock: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 paint 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_INTERFACE (GdkPaintClock, gdk_paint_clock, G_TYPE_OBJECT)
+
+enum {
+  FRAME_REQUESTED,
+  BEFORE_PAINT,
+  PAINT,
+  AFTER_PAINT,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+gdk_paint_clock_default_init (GdkPaintClockInterface *iface)
+{
+  /**
+   * GdkPaintClock::frame-requested:
+   * @clock: the paint clock emitting the signal
+   *
+   * This signal is emitted when a frame is not pending, and
+   * gdk_paint_clock_request_frame() is called to request a frame.
+   */
+  signals[FRAME_REQUESTED] =
+    g_signal_new (g_intern_static_string ("frame-requested"),
+                  GDK_TYPE_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::before-paint:
+   * @clock: the paint 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_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::paint:
+   * @clock: the paint 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_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::after-paint:
+   * @clock: the paint 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_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+/**
+ * gdk_paint_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 paint clock times.
+ *
+ *
+ * Since: 3.0
+ * Return value: a timestamp in milliseconds
+ */
+guint64
+gdk_paint_clock_get_frame_time (GdkPaintClock *clock)
+{
+  g_return_val_if_fail (GDK_IS_PAINT_CLOCK (clock), 0);
+
+  return GDK_PAINT_CLOCK_GET_IFACE (clock)->get_frame_time (clock);
+}
+
+/**
+ * gdk_paint_clock_request_frame:
+ * @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_paint_clock_request_frame (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  GDK_PAINT_CLOCK_GET_IFACE (clock)->request_frame (clock);
+}
+
+/**
+ * gdk_paint_clock_get_frame_requested:
+ * @clock: the clock
+ *
+ * Gets whether a frame paint has been requested but has not been
+ * performed.
+ *
+ *
+ * Since: 3.0
+ * Return value: TRUE if a frame paint is pending
+ */
+gboolean
+gdk_paint_clock_get_frame_requested (GdkPaintClock *clock)
+{
+  g_return_val_if_fail (GDK_IS_PAINT_CLOCK (clock), FALSE);
+
+  return GDK_PAINT_CLOCK_GET_IFACE (clock)->get_frame_requested (clock);
+}
+
+/**
+ * gdk_paint_clock_get_frame_time_val:
+ * @clock: the clock
+ * @timeval: #GTimeVal to fill in with frame time
+ *
+ * Like gdk_paint_clock_get_frame_time() but returns the time as a
+ * #GTimeVal which may be handy with some APIs (such as
+ * #GdkPixbufAnimation).
+ */
+void
+gdk_paint_clock_get_frame_time_val (GdkPaintClock *clock,
+                                    GTimeVal      *timeval)
+{
+  guint64 time_ms;
+
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  time_ms = gdk_paint_clock_get_frame_time (clock);
+
+  timeval->tv_sec = time_ms / 1000;
+  timeval->tv_usec = (time_ms % 1000) * 1000;
+}
+
+/**
+ * gdk_paint_clock_frame_requested:
+ * @clock: the clock
+ *
+ * Emits the frame-requested signal. Used in implementations of the
+ * #GdkPaintClock interface.
+ */
+void
+gdk_paint_clock_frame_requested (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[FRAME_REQUESTED], 0);
+}
+
+/**
+ * gdk_paint_clock_paint:
+ * @clock: the clock
+ *
+ * Emits the before-paint, paint, and after-paint signals. Used in
+ * implementations of the #GdkPaintClock interface.
+ */
+void
+gdk_paint_clock_paint (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[BEFORE_PAINT], 0);
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[PAINT], 0);
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[AFTER_PAINT], 0);
+}
diff --git a/gdk/gdkpaintclock.h b/gdk/gdkpaintclock.h
new file mode 100644
index 0000000..0741722
--- /dev/null
+++ b/gdk/gdkpaintclock.h
@@ -0,0 +1,77 @@
+/* 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/.
+ */
+
+#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
+#error "Only <gdk/gdk.h> can be included directly."
+#endif
+
+#ifndef __GDK_PAINT_CLOCK_H__
+#define __GDK_PAINT_CLOCK_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_PAINT_CLOCK             (gdk_paint_clock_get_type ())
+#define GDK_PAINT_CLOCK(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_PAINT_CLOCK, GdkPaintClock))
+#define GDK_IS_PAINT_CLOCK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_PAINT_CLOCK))
+#define GDK_PAINT_CLOCK_GET_IFACE(inst)  (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GDK_TYPE_PAINT_CLOCK, GdkPaintClockInterface))
+
+typedef struct _GdkPaintClock          GdkPaintClock;
+typedef struct _GdkPaintClockInterface GdkPaintClockInterface;
+
+struct _GdkPaintClockInterface
+{
+  GTypeInterface		   base_iface;
+
+  guint64  (* get_frame_time)      (GdkPaintClock *clock);
+  void     (* request_frame)       (GdkPaintClock *clock);
+  gboolean (* get_frame_requested) (GdkPaintClock *clock);
+
+  /* signals */
+  /* void (* frame_requested)    (GdkPaintClock *clock); */
+  /* void (* before_paint)       (GdkPaintClock *clock); */
+  /* void (* paint)              (GdkPaintClock *clock); */
+  /* void (* after_paint)        (GdkPaintClock *clock); */
+};
+
+GType    gdk_paint_clock_get_type             (void) G_GNUC_CONST;
+
+guint64  gdk_paint_clock_get_frame_time      (GdkPaintClock *clock);
+void     gdk_paint_clock_request_frame       (GdkPaintClock *clock);
+gboolean gdk_paint_clock_get_frame_requested (GdkPaintClock *clock);
+
+/* Convenience API */
+void  gdk_paint_clock_get_frame_time_val (GdkPaintClock  *clock,
+                                          GTimeVal       *timeval);
+
+/* Signal emitters (used in paint clock implementations) */
+void     gdk_paint_clock_frame_requested     (GdkPaintClock *clock);
+void     gdk_paint_clock_paint               (GdkPaintClock *clock);
+
+G_END_DECLS
+
+#endif /* __GDK_PAINT_CLOCK_H__ */
diff --git a/gdk/gdkpaintclockidle.c b/gdk/gdkpaintclockidle.c
new file mode 100644
index 0000000..c967af5
--- /dev/null
+++ b/gdk/gdkpaintclockidle.c
@@ -0,0 +1,182 @@
+/* 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 "gdkpaintclockidle.h"
+#include "gdk.h"
+
+struct _GdkPaintClockIdlePrivate
+{
+  GTimer *timer;
+  /* timer_base is used to avoid ever going backward */
+  guint64 timer_base;
+  guint64 frame_time;
+
+  guint idle_id;
+
+  unsigned int in_paint : 1;
+};
+
+static void gdk_paint_clock_idle_finalize             (GObject                *object);
+static void gdk_paint_clock_idle_interface_init       (GdkPaintClockInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdkPaintClockIdle, gdk_paint_clock_idle, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINT_CLOCK,
+						gdk_paint_clock_idle_interface_init))
+
+static void
+gdk_paint_clock_idle_class_init (GdkPaintClockIdleClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = gdk_paint_clock_idle_finalize;
+
+  g_type_class_add_private (klass, sizeof (GdkPaintClockIdlePrivate));
+}
+
+static void
+gdk_paint_clock_idle_init (GdkPaintClockIdle *paint_clock_idle)
+{
+  GdkPaintClockIdlePrivate *priv;
+
+  paint_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (paint_clock_idle,
+                                                        GDK_TYPE_PAINT_CLOCK_IDLE,
+                                                        GdkPaintClockIdlePrivate);
+  priv = paint_clock_idle->priv;
+
+  priv->timer = g_timer_new ();
+}
+
+static void
+gdk_paint_clock_idle_finalize (GObject *object)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (object)->priv;
+
+  g_timer_destroy (priv->timer);
+
+  G_OBJECT_CLASS (gdk_paint_clock_idle_parent_class)->finalize (object);
+}
+
+static guint64
+compute_frame_time (GdkPaintClockIdle *idle)
+{
+  GdkPaintClockIdlePrivate *priv = idle->priv;
+  guint64 computed_frame_time;
+  guint64 elapsed;
+
+  elapsed = ((guint64) (g_timer_elapsed (priv->timer, NULL) * 1000)) + priv->timer_base;
+  if (elapsed < priv->frame_time)
+    {
+      /* clock went backward. adapt to that by forevermore increasing
+       * timer_base.  For now, assume we've gone forward in time 1ms.
+       */
+      /* hmm. just fix GTimer? */
+      computed_frame_time = priv->frame_time + 1;
+      priv->timer_base += (priv->frame_time - elapsed) + 1;
+    }
+  else
+    {
+      computed_frame_time = elapsed;
+    }
+
+  return computed_frame_time;
+}
+
+static guint64
+gdk_paint_clock_idle_get_frame_time (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+  guint64 computed_frame_time;
+
+  /* can't change frame time during a paint */
+  if (priv->in_paint)
+    return priv->frame_time;
+
+  /* Outside a paint, pick something close to "now" */
+  computed_frame_time = compute_frame_time (GDK_PAINT_CLOCK_IDLE (clock));
+
+  /* 16ms is 60fps. We only update frame time that often because we'd
+   * like to try to keep animations on the same start times.
+   * get_frame_time() would normally be used outside of a paint to
+   * record an animation start time for example.
+   */
+  if ((computed_frame_time - priv->frame_time) > 16)
+    priv->frame_time = computed_frame_time;
+
+  return priv->frame_time;
+}
+
+static gboolean
+gdk_paint_clock_paint_idle (void *data)
+{
+  GdkPaintClock *clock = GDK_PAINT_CLOCK (data);
+  GdkPaintClockIdle *clock_idle = GDK_PAINT_CLOCK_IDLE (clock);
+  GdkPaintClockIdlePrivate *priv = clock_idle->priv;
+
+  priv->idle_id = 0;
+
+  priv->in_paint = TRUE;
+  priv->frame_time = compute_frame_time (clock_idle);
+
+  gdk_paint_clock_paint (clock);
+
+  priv->in_paint = FALSE;
+
+  return FALSE;
+}
+
+static void
+gdk_paint_clock_idle_request_frame (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+
+  if (priv->idle_id == 0)
+    {
+      priv->idle_id = gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
+                                                 gdk_paint_clock_paint_idle,
+                                                 g_object_ref (clock),
+                                                 (GDestroyNotify) g_object_unref);
+
+      gdk_paint_clock_frame_requested (clock);
+    }
+}
+
+static gboolean
+gdk_paint_clock_idle_get_frame_requested (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+
+  return priv->idle_id != 0;
+}
+
+static void
+gdk_paint_clock_idle_interface_init (GdkPaintClockInterface *iface)
+{
+  iface->get_frame_time = gdk_paint_clock_idle_get_frame_time;
+  iface->request_frame = gdk_paint_clock_idle_request_frame;
+  iface->get_frame_requested = gdk_paint_clock_idle_get_frame_requested;
+}
diff --git a/gdk/gdkpaintclockidle.h b/gdk/gdkpaintclockidle.h
new file mode 100644
index 0000000..f87e0e6
--- /dev/null
+++ b/gdk/gdkpaintclockidle.h
@@ -0,0 +1,65 @@
+/* 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/.
+ */
+
+/* Uninstalled header, internal to GDK */
+
+#ifndef __GDK_PAINT_CLOCK_IDLE_H__
+#define __GDK_PAINT_CLOCK_IDLE_H__
+
+#include <gdk/gdkpaintclock.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_PAINT_CLOCK_IDLE            (gdk_paint_clock_idle_get_type ())
+#define GDK_PAINT_CLOCK_IDLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdle))
+#define GDK_PAINT_CLOCK_IDLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdleClass))
+#define GDK_IS_PAINT_CLOCK_IDLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_PAINT_CLOCK_IDLE))
+#define GDK_IS_PAINT_CLOCK_IDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_PAINT_CLOCK_IDLE))
+#define GDK_PAINT_CLOCK_IDLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdleClass))
+
+
+typedef struct _GdkPaintClockIdle              GdkPaintClockIdle;
+typedef struct _GdkPaintClockIdlePrivate       GdkPaintClockIdlePrivate;
+typedef struct _GdkPaintClockIdleClass         GdkPaintClockIdleClass;
+
+struct _GdkPaintClockIdle
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  GdkPaintClockIdlePrivate *priv;
+};
+
+struct _GdkPaintClockIdleClass
+{
+  GObjectClass parent_class;
+};
+
+GType	   gdk_paint_clock_idle_get_type          (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* __GDK_PAINT_CLOCK_IDLE_H__ */
diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c
index 1479798..f032756 100644
--- a/gdk/gdkwindow.c
+++ b/gdk/gdkwindow.c
@@ -41,6 +41,7 @@
 #include "gdkdeviceprivate.h"
 #include "gdkdrawable.h"
 #include "gdkmarshalers.h"
+#include "gdkpaintclockidle.h"
 #include "gdkscreen.h"
 #include "gdkwindowimpl.h"
 
@@ -190,7 +191,8 @@ enum {
 
 enum {
   PROP_0,
-  PROP_CURSOR
+  PROP_CURSOR,
+  PROP_PAINT_CLOCK
 };
 
 typedef enum {
@@ -267,6 +269,9 @@ static void gdk_window_invalidate_rect_full (GdkWindow          *window,
 					     const GdkRectangle *rect,
 					     gboolean            invalidate_children,
 					     ClearBg             clear_bg);
+static void gdk_window_process_all_updates_internal (gboolean default_clock_only);
+
+static void gdk_ensure_default_paint_clock  (void);
 
 static guint signals[LAST_SIGNAL] = { 0 };
 
@@ -395,6 +400,23 @@ gdk_window_class_init (GdkWindowObjectClass *klass)
                                                        G_PARAM_READWRITE));
 
   /**
+   * GdkWindow:paint-clock:
+   *
+   * The paint clock for a #GdkWindow, see #GdkPaintClock
+   *
+   * The paint clock remains the same for the lifetime of the window.
+   *
+   * Since: 3.0
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_PAINT_CLOCK,
+                                   g_param_spec_object ("paint-clock",
+                                                        P_("Paint clock"),
+                                                        P_("Paint clock"),
+                                                        GDK_TYPE_PAINT_CLOCK,
+                                                        G_PARAM_READWRITE));
+
+  /**
    * GdkWindow::pick-embedded-child:
    * @window: the window on which the signal is emitted
    * @x: x coordinate in the window
@@ -567,6 +589,10 @@ gdk_window_set_property (GObject      *object,
       gdk_window_set_cursor (window, g_value_get_boxed (value));
       break;
 
+    case PROP_PAINT_CLOCK:
+      gdk_window_set_paint_clock (window, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -587,6 +613,10 @@ gdk_window_get_property (GObject    *object,
       g_value_set_boxed (value, gdk_window_get_cursor (window));
       break;
 
+    case PROP_PAINT_CLOCK:
+      g_value_set_object (value, gdk_window_get_paint_clock (window));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -3706,7 +3736,7 @@ gdk_window_set_cairo_clip (GdkDrawable *drawable,
 /* Code for dirty-region queueing
  */
 static GSList *update_windows = NULL;
-static guint update_idle = 0;
+static GdkPaintClock *_gdk_default_paint_clock = NULL;
 static gboolean debug_updates = FALSE;
 
 static inline gboolean
@@ -3805,12 +3835,25 @@ gdk_window_remove_update_window (GdkWindow *window)
   update_windows = g_slist_remove (update_windows, window);
 }
 
-static gboolean
-gdk_window_update_idle (gpointer data)
+static void
+gdk_window_paint_default_clock_updates (gpointer data)
 {
-  gdk_window_process_all_updates ();
+  gdk_window_process_all_updates_internal (TRUE);
+}
 
-  return FALSE;
+static void
+gdk_ensure_default_paint_clock (void)
+{
+  if (_gdk_default_paint_clock == NULL)
+    {
+      _gdk_default_paint_clock = g_object_new (GDK_TYPE_PAINT_CLOCK_IDLE,
+                                               NULL);
+
+      g_signal_connect (G_OBJECT (_gdk_default_paint_clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_default_clock_updates),
+                        NULL);
+    }
 }
 
 static gboolean
@@ -3831,11 +3874,7 @@ gdk_window_schedule_update (GdkWindow *window)
        gdk_window_is_toplevel_frozen (window)))
     return;
 
-  if (!update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-				 gdk_window_update_idle,
-				 NULL, NULL);
+  gdk_paint_clock_request_frame (gdk_window_get_paint_clock (window));
 }
 
 void
@@ -4128,6 +4167,19 @@ flush_all_displays (void)
   g_slist_free (displays);
 }
 
+/**
+ * gdk_window_process_all_updates:
+ *
+ * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
+ * in the application.
+ *
+ **/
+void
+gdk_window_process_all_updates (void)
+{
+  gdk_window_process_all_updates_internal (FALSE);
+}
+
 /* Currently it is not possible to override
  * gdk_window_process_all_updates in the same manner as
  * gdk_window_process_updates and gdk_window_invalidate_maybe_recurse
@@ -4138,15 +4190,8 @@ flush_all_displays (void)
  * displays and call the mehod.
  */
 
-/**
- * gdk_window_process_all_updates:
- *
- * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
- * in the application.
- *
- **/
-void
-gdk_window_process_all_updates (void)
+static void
+gdk_window_process_all_updates_internal (gboolean default_clock_only)
 {
   GSList *old_update_windows = update_windows;
   GSList *tmp_list = update_windows;
@@ -4158,18 +4203,13 @@ gdk_window_process_all_updates (void)
       /* We can't do this now since that would recurse, so
 	 delay it until after the recursion is done. */
       got_recursive_update = TRUE;
-      update_idle = 0;
       return;
     }
 
   in_process_all_updates = TRUE;
   got_recursive_update = FALSE;
 
-  if (update_idle)
-    g_source_remove (update_idle);
-
   update_windows = NULL;
-  update_idle = 0;
 
   _gdk_windowing_before_process_all_updates ();
 
@@ -4182,7 +4222,8 @@ gdk_window_process_all_updates (void)
       if (!GDK_WINDOW_DESTROYED (tmp_list->data))
 	{
 	  if (private->update_freeze_count ||
-	      gdk_window_is_toplevel_frozen (tmp_list->data))
+	      gdk_window_is_toplevel_frozen (tmp_list->data) ||
+              (default_clock_only && private->paint_clock == NULL))
 	    gdk_window_add_update_window ((GdkWindow *) private);
 	  else
 	    gdk_window_process_updates_internal (tmp_list->data);
@@ -4204,31 +4245,20 @@ gdk_window_process_all_updates (void)
      redraw now so that it eventually happens,
      otherwise we could miss an update if nothing
      else schedules an update. */
-  if (got_recursive_update && !update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-				 gdk_window_update_idle,
-				 NULL, NULL);
+  if (got_recursive_update)
+    gdk_window_schedule_update (NULL);
 }
 
-/**
- * gdk_window_process_updates:
- * @window: a #GdkWindow
- * @update_children: whether to also process updates for child windows
- *
- * Sends one or more expose events to @window. The areas in each
- * expose event will cover the entire update area for the window (see
- * gdk_window_invalidate_region() for details). Normally GDK calls
- * gdk_window_process_all_updates() on your behalf, so there's no
- * need to call this function unless you want to force expose events
- * to be delivered immediately and synchronously (vs. the usual
- * case, where GDK delivers them in an idle handler). Occasionally
- * this is useful to produce nicer scrolling behavior, for example.
- *
- **/
-void
-gdk_window_process_updates (GdkWindow *window,
-			    gboolean   update_children)
+
+enum {
+  PROCESS_UPDATES_NO_RECURSE,
+  PROCESS_UPDATES_WITH_ALL_CHILDREN,
+  PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN
+};
+
+static void
+gdk_window_process_updates_with_mode (GdkWindow     *window,
+                                      int            recurse_mode)
 {
   GdkWindowObject *private = (GdkWindowObject *)window;
   GdkWindowObject *impl_window;
@@ -4255,7 +4285,7 @@ gdk_window_process_updates (GdkWindow *window,
       gdk_window_remove_update_window ((GdkWindow *)impl_window);
     }
 
-  if (update_children)
+  if (recurse_mode != PROCESS_UPDATES_NO_RECURSE)
     {
       /* process updates in reverse stacking order so composition or
        * painting over achieves the desired effect for offscreen windows
@@ -4267,8 +4297,14 @@ gdk_window_process_updates (GdkWindow *window,
 
       for (node = g_list_last (children); node; node = node->prev)
 	{
-	  gdk_window_process_updates (node->data, TRUE);
-	  g_object_unref (node->data);
+          GdkWindow *child = node->data;
+          if (recurse_mode == PROCESS_UPDATES_WITH_ALL_CHILDREN ||
+              (recurse_mode == PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN &&
+               ((GdkWindowObject*)child)->paint_clock == NULL))
+            {
+              gdk_window_process_updates (child, TRUE);
+            }
+	  g_object_unref (child);
 	}
 
       g_list_free (children);
@@ -4277,6 +4313,33 @@ gdk_window_process_updates (GdkWindow *window,
   g_object_unref (window);
 }
 
+/**
+ * gdk_window_process_updates:
+ * @window: a #GdkWindow
+ * @update_children: whether to also process updates for child windows
+ *
+ * Sends one or more expose events to @window. The areas in each
+ * expose event will cover the entire update area for the window (see
+ * gdk_window_invalidate_region() for details). Normally GDK calls
+ * gdk_window_process_all_updates() on your behalf, so there's no
+ * need to call this function unless you want to force expose events
+ * to be delivered immediately and synchronously (vs. the usual
+ * case, where GDK delivers them in an idle handler). Occasionally
+ * this is useful to produce nicer scrolling behavior, for example.
+ *
+ **/
+void
+gdk_window_process_updates (GdkWindow *window,
+			    gboolean   update_children)
+{
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  return gdk_window_process_updates_with_mode (window,
+                                               update_children ?
+                                               PROCESS_UPDATES_WITH_ALL_CHILDREN :
+                                               PROCESS_UPDATES_NO_RECURSE);
+}
+
 static void
 gdk_window_invalidate_rect_full (GdkWindow          *window,
 				  const GdkRectangle *rect,
@@ -9917,3 +9980,118 @@ gdk_window_create_similar_surface (GdkWindow *     window,
   return surface;
 }
 
+static void
+gdk_window_paint_on_clock (void *data)
+{
+  GdkWindow *window;
+
+  window = GDK_WINDOW (data);
+
+  /* Update window and any children on the same clock.
+   */
+  gdk_window_process_updates_with_mode (window, PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN);
+}
+
+/**
+ * gdk_window_set_paint_clock:
+ * @window: window to set paint clock on
+ * @clock: the clock
+ *
+ * Sets the paint clock for the window. The paint clock for a window
+ * cannot be changed while the window is mapped. Set the paint
+ * clock to #NULL to use the default paint clock. (By default the
+ * paint clock comes from the window's parent or is a global default
+ * paint clock.)
+ *
+ * Since: 3.0
+ */
+void
+gdk_window_set_paint_clock (GdkWindow     *window,
+                            GdkPaintClock *clock)
+{
+  GdkWindowObject *private;
+
+  g_return_if_fail (GDK_IS_WINDOW (window));
+  g_return_if_fail (clock == NULL || GDK_IS_PAINT_CLOCK (clock));
+  g_return_if_fail (!GDK_WINDOW_IS_MAPPED (window));
+
+  private = (GdkWindowObject*) window;
+
+  if (clock == private->paint_clock)
+    return;
+
+  /* If we are using our parent's clock, then the parent will repaint
+   * us when that clock fires. If we are using the default clock, then
+   * it does a gdk_window_process_all_updates() which will repaint us
+   * when the clock fires. If we are using our own clock, then we have
+   * to connect to "paint" on it ourselves and paint ourselves and
+   * any child windows.
+   */
+
+  if (clock)
+    {
+      g_object_ref (clock);
+      g_signal_connect (G_OBJECT (clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_on_clock),
+                        window);
+    }
+
+  if (private->paint_clock)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (private->paint_clock),
+                                            G_CALLBACK (gdk_window_paint_on_clock),
+                                            window);
+      g_object_unref (private->paint_clock);
+    }
+
+  private->paint_clock = clock;
+  g_object_notify (G_OBJECT (window), "paint-clock");
+
+  /* We probably should recurse child windows and emit notify on their
+   * paint-clock properties also, and we should emit notify when a
+   * window is first parented.
+   */
+}
+
+/**
+ * gdk_window_get_paint_clock:
+ * @window: window to get paint clock for
+ *
+ * Gets the paint clock for the window. The paint clock for a window
+ * never changes while the window is mapped. It may be changed at
+ * other times.
+ *
+ * Since: 3.0
+ * Return value: the paint clock
+ */
+GdkPaintClock*
+gdk_window_get_paint_clock (GdkWindow *window)
+{
+  GdkWindowObject *private;
+
+  g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+
+  private = (GdkWindowObject*) window;
+  if (private->paint_clock != NULL)
+    {
+      /* Paint clock set explicitly on this window */
+      return private->paint_clock;
+    }
+  else
+    {
+      GdkWindow *parent;
+
+      /* parent's paint clock or default */
+      parent = gdk_window_get_effective_parent (window);
+      if (parent != NULL)
+        {
+          return gdk_window_get_paint_clock (parent);
+        }
+      else
+        {
+          gdk_ensure_default_paint_clock ();
+          return _gdk_default_paint_clock;
+        }
+    }
+}
diff --git a/gdk/gdkwindow.h b/gdk/gdkwindow.h
index 84fc1e8..d1323c4 100644
--- a/gdk/gdkwindow.h
+++ b/gdk/gdkwindow.h
@@ -32,6 +32,7 @@
 #define __GDK_WINDOW_H__
 
 #include <gdk/gdkdrawable.h>
+#include <gdk/gdkpaintclock.h>
 #include <gdk/gdktypes.h>
 #include <gdk/gdkevents.h>
 
@@ -850,6 +851,11 @@ void       gdk_window_set_support_multidevice (GdkWindow *window,
                                                gboolean   support_multidevice);
 gboolean   gdk_window_get_support_multidevice (GdkWindow *window);
 
+/* Paint clock */
+void           gdk_window_set_paint_clock      (GdkWindow     *window,
+                                                GdkPaintClock *clock);
+GdkPaintClock* gdk_window_get_paint_clock      (GdkWindow     *window);
+
 G_END_DECLS
 
 #endif /* __GDK_WINDOW_H__ */
diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c
index 5f92f32..6920e13 100644
--- a/gtk/gtkcontainer.c
+++ b/gtk/gtkcontainer.c
@@ -94,6 +94,10 @@ static void     gtk_container_add_unimplemented    (GtkContainer      *container
 static void     gtk_container_remove_unimplemented (GtkContainer      *container,
 						    GtkWidget         *widget);
 static void     gtk_container_real_check_resize    (GtkContainer      *container);
+
+static GdkPaintClock* gtk_container_real_get_paint_clock (GtkContainer *container,
+                                                          GtkWidget    *child);
+
 static gboolean gtk_container_focus                (GtkWidget         *widget,
 						    GtkDirectionType   direction);
 static void     gtk_container_real_set_focus_child (GtkContainer      *container,
@@ -252,6 +256,7 @@ gtk_container_class_init (GtkContainerClass *class)
   class->set_focus_child = gtk_container_real_set_focus_child;
   class->child_type = NULL;
   class->composite_name = gtk_container_child_default_composite_name;
+  class->get_paint_clock = gtk_container_real_get_paint_clock;
 
   g_object_class_install_property (gobject_class,
                                    PROP_RESIZE_MODE,
@@ -1532,6 +1537,57 @@ gtk_container_resize_children (GtkContainer *container)
   gtk_widget_set_allocation (widget, &allocation);
 }
 
+static GdkPaintClock*
+gtk_container_real_get_paint_clock (GtkContainer *container,
+                                    GtkWidget    *child)
+{
+  GdkWindow *window;
+
+  g_assert (gtk_widget_get_realized (GTK_WIDGET (container)));
+  g_assert (gtk_widget_get_realized (child));
+  g_assert (gtk_widget_get_parent (child) == GTK_WIDGET (container));
+
+  /* By default we just get the clock on the child's window. The idea
+   * however is that some containers (especially those that are
+   * proxies for another toolkit system or things like GL areas) might
+   * provide a special clock. No-window children would of course have
+   * the container's window so get the container's clock. Window
+   * children also inherit from container by default because GdkWindow
+   * looks up its hierarchy by default.
+   *
+   * Containers must not change the child's clock while the child is
+   * mapped.
+   */
+  window = gtk_widget_get_window (child);
+  g_assert (window != NULL);
+  return gdk_window_get_paint_clock (window);
+}
+
+/**
+ * gtk_container_get_child_paint_clock:
+ * @container: the container
+ * @child: a child of the container
+ *
+ * Use gtk_widget_get_paint_clock() rather than this function,
+ * this is internal to GTK+.
+ *
+ * Return value: paint clock (or #NULL if child is not realized)
+ */
+GdkPaintClock*
+_gtk_container_get_child_paint_clock (GtkContainer *container,
+                                      GtkWidget    *child)
+{
+  g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
+  g_return_val_if_fail (GTK_IS_WIDGET (child), NULL);
+  g_return_val_if_fail (gtk_widget_get_parent (child) == GTK_WIDGET (container), NULL);
+
+  if (!(gtk_widget_get_realized (container) &&
+        gtk_widget_get_realized (child)))
+    return NULL;
+
+  return GTK_CONTAINER_GET_CLASS (container)->get_child_paint_clock (container, child);
+}
+
 static void
 gtk_container_adjust_size_request (GtkWidget         *widget,
                                    GtkOrientation     orientation,
diff --git a/gtk/gtkcontainer.h b/gtk/gtkcontainer.h
index 8742911..761d7f6 100644
--- a/gtk/gtkcontainer.h
+++ b/gtk/gtkcontainer.h
@@ -89,6 +89,9 @@ struct _GtkContainerClass
 				 GValue          *value,
 				 GParamSpec      *pspec);
 
+  GdkPaintClock* (* get_child_paint_clock) (GtkContainer *container,
+                                            GtkWidget    *child);
+
   /* Padding for future expansion */
   void (*_gtk_reserved1) (void);
   void (*_gtk_reserved2) (void);
@@ -199,6 +202,8 @@ void    gtk_container_forall		     (GtkContainer *container,
 void    gtk_container_class_handle_border_width (GtkContainerClass *klass);
 
 /* Non-public methods */
+GdkPaintClock* _gtk_container_get_child_paint_clock (GtkContainer *container,
+                                                     GtkWidget    *child);
 void	_gtk_container_queue_resize	     (GtkContainer *container);
 void    _gtk_container_clear_resize_widgets   (GtkContainer *container);
 gchar*	_gtk_container_child_composite_name   (GtkContainer *container,
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 2f33f8b..e6ad1d8 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -4185,6 +4185,52 @@ gtk_widget_queue_resize_no_redraw (GtkWidget *widget)
 }
 
 /**
+ * gtk_widget_get_paint_clock:
+ * @widget: a #GtkWidget
+ *
+ * Obtains the paint clock for a widget. The paint clock is a global
+ * "ticker" that can be used to drive animations and repaints.  The
+ * most common reason to get the paint clock is to call
+ * gdk_paint_clock_get_frame_time(), in order to get a time to use for
+ * animating. For example you might record the start of the animation
+ * with an initial value from gdk_paint_clock_get_frame_time(), and
+ * then update the animation by calling
+ * gdk_paint_clock_get_frame_time() again during each repaint.
+ *
+ * gdk_paint_clock_request_frame() will result in a new frame on the
+ * clock, but won't necessarily repaint any widgets. To repaint a
+ * widget, you have to use gtk_widget_queue_draw() which invalidates
+ * the widget (thus scheduling it to receive a draw on the next
+ * frame). gtk_widget_queue_draw() will also end up requesting a frame
+ * on the appropriate paint clock.
+ *
+ * A widget's paint clock will not change while the widget is
+ * mapped. Reparenting a widget (which implies a temporary unmap) can
+ * change the widget's paint clock.
+ *
+ * Unrealized widgets do not have a paint clock.
+ *
+ * Since: 3.0
+ * Return value: a #GdkPaintClock (or #NULL if widget is unrealized)
+ */
+GdkPaintClock*
+gtk_widget_get_paint_clock (GtkWidget *widget)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+
+  if (widget->priv->realized)
+    {
+      g_assert (widget->priv->parent != NULL);
+      return gtk_container_get_child_paint_clock (GTK_CONTAINER (widget->priv->parent),
+                                                  widget);
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+/**
  * gtk_widget_size_request:
  * @widget: a #GtkWidget
  * @requisition: (out): a #GtkRequisition to be filled in
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index c94dd6a..6289ac2 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -438,6 +438,9 @@ void	   gtk_widget_queue_draw_area	  (GtkWidget	       *widget,
 					   gint                 height);
 void	   gtk_widget_queue_resize	  (GtkWidget	       *widget);
 void	   gtk_widget_queue_resize_no_redraw (GtkWidget *widget);
+
+GdkPaintClock* gtk_widget_get_paint_clock (GtkWidget           *widget);
+
 #ifndef GTK_DISABLE_DEPRECATED
 void	   gtk_widget_size_request	  (GtkWidget	       *widget,
 					   GtkRequisition      *requisition);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]