]> Pileus Git - ~andy/gtk/blob - gdk/gdkframeclockidle.c
GdkWindowX11: Only start a frame when we emit damage
[~andy/gtk] / gdk / gdkframeclockidle.c
1 /* GDK - The GIMP Drawing Kit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
25  */
26
27 #include "config.h"
28
29 #include "gdkframeclockidle.h"
30 #include "gdk.h"
31
32 #define FRAME_INTERVAL 16667 // microseconds
33
34 struct _GdkFrameClockIdlePrivate
35 {
36   GTimer *timer;
37   /* timer_base is used to avoid ever going backward */
38   guint64 timer_base;
39   guint64 frame_time;
40   guint64 min_next_frame_time;
41
42   guint flush_idle_id;
43   guint paint_idle_id;
44   guint freeze_count;
45
46   GdkFrameClockPhase requested;
47   GdkFrameClockPhase phase;
48 };
49
50 static gboolean gdk_frame_clock_flush_idle (void *data);
51 static gboolean gdk_frame_clock_paint_idle (void *data);
52
53 static void gdk_frame_clock_idle_finalize             (GObject                *object);
54 static void gdk_frame_clock_idle_interface_init       (GdkFrameClockInterface *iface);
55
56 G_DEFINE_TYPE_WITH_CODE (GdkFrameClockIdle, gdk_frame_clock_idle, G_TYPE_OBJECT,
57                          G_IMPLEMENT_INTERFACE (GDK_TYPE_FRAME_CLOCK,
58                                                 gdk_frame_clock_idle_interface_init))
59
60 static void
61 gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
62 {
63   GObjectClass *gobject_class = (GObjectClass*) klass;
64
65   gobject_class->finalize     = gdk_frame_clock_idle_finalize;
66
67   g_type_class_add_private (klass, sizeof (GdkFrameClockIdlePrivate));
68 }
69
70 static void
71 gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
72 {
73   GdkFrameClockIdlePrivate *priv;
74
75   frame_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (frame_clock_idle,
76                                                         GDK_TYPE_FRAME_CLOCK_IDLE,
77                                                         GdkFrameClockIdlePrivate);
78   priv = frame_clock_idle->priv;
79
80   priv->timer = g_timer_new ();
81   priv->freeze_count = 0;
82 }
83
84 static void
85 gdk_frame_clock_idle_finalize (GObject *object)
86 {
87   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (object)->priv;
88
89   g_timer_destroy (priv->timer);
90
91   G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->finalize (object);
92 }
93
94 static guint64
95 compute_frame_time (GdkFrameClockIdle *idle)
96 {
97   GdkFrameClockIdlePrivate *priv = idle->priv;
98   guint64 computed_frame_time;
99   guint64 elapsed;
100
101   elapsed = g_get_monotonic_time () + priv->timer_base;
102   if (elapsed < priv->frame_time)
103     {
104       /* clock went backward. adapt to that by forevermore increasing
105        * timer_base.  For now, assume we've gone forward in time 1ms.
106        */
107       /* hmm. just fix GTimer? */
108       computed_frame_time = priv->frame_time + 1;
109       priv->timer_base += (priv->frame_time - elapsed) + 1;
110     }
111   else
112     {
113       computed_frame_time = elapsed;
114     }
115
116   return computed_frame_time;
117 }
118
119 static guint64
120 gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock)
121 {
122   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
123   guint64 computed_frame_time;
124
125   /* can't change frame time during a paint */
126   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE &&
127       priv->phase != GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS)
128     return priv->frame_time;
129
130   /* Outside a paint, pick something close to "now" */
131   computed_frame_time = compute_frame_time (GDK_FRAME_CLOCK_IDLE (clock));
132
133   /* 16ms is 60fps. We only update frame time that often because we'd
134    * like to try to keep animations on the same start times.
135    * get_frame_time() would normally be used outside of a paint to
136    * record an animation start time for example.
137    */
138   if ((computed_frame_time - priv->frame_time) > FRAME_INTERVAL)
139     priv->frame_time = computed_frame_time;
140
141   return priv->frame_time;
142 }
143
144 static void
145 maybe_start_idle (GdkFrameClockIdle *clock_idle)
146 {
147   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
148
149   if (priv->freeze_count == 0)
150     {
151       guint min_interval = 0;
152
153       if (priv->min_next_frame_time != 0)
154         {
155           guint64 now = compute_frame_time (clock_idle);
156           guint64 min_interval_us = MAX (priv->min_next_frame_time, now) - now;
157           min_interval = (min_interval_us + 500) / 1000;
158         }
159
160       if (priv->flush_idle_id == 0 &&
161           (priv->requested & GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
162         {
163           priv->flush_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS + 1,
164                                                               min_interval,
165                                                               gdk_frame_clock_flush_idle,
166                                                               g_object_ref (clock_idle),
167                                                               (GDestroyNotify) g_object_unref);
168         }
169
170       if (priv->paint_idle_id == 0 &&
171           (priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
172         {
173           priv->paint_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_REDRAW,
174                                                               min_interval,
175                                                               gdk_frame_clock_paint_idle,
176                                                               g_object_ref (clock_idle),
177                                                               (GDestroyNotify) g_object_unref);
178
179           gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle));
180         }
181     }
182 }
183
184 static gboolean
185 gdk_frame_clock_flush_idle (void *data)
186 {
187   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
188   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
189   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
190
191   priv->flush_idle_id = 0;
192
193   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE)
194     return FALSE;
195
196   priv->phase = GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
197   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
198
199   g_signal_emit_by_name (G_OBJECT (clock), "flush-events");
200
201   if ((priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
202     priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
203   else
204     priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
205
206   return FALSE;
207 }
208
209 static gboolean
210 gdk_frame_clock_paint_idle (void *data)
211 {
212   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
213   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
214   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
215   gboolean skip_to_resume_events;
216
217   priv->paint_idle_id = 0;
218
219   skip_to_resume_events =
220     (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
221
222   if (!skip_to_resume_events)
223     {
224       switch (priv->phase)
225         {
226         case GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS:
227           break;
228         case GDK_FRAME_CLOCK_PHASE_NONE:
229         case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT:
230           if (priv->freeze_count == 0)
231             {
232               priv->frame_time = compute_frame_time (clock_idle);
233
234               priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
235
236               /* We always emit ::before-paint and ::after-paint if
237                * any of the intermediate phases are requested and
238                * they don't get repeated if you freeze/thaw while
239                * in them. */
240               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
241               g_signal_emit_by_name (G_OBJECT (clock), "before-paint");
242               priv->phase = GDK_FRAME_CLOCK_PHASE_UPDATE;
243             }
244         case GDK_FRAME_CLOCK_PHASE_UPDATE:
245           if (priv->freeze_count == 0)
246             {
247               if (priv->requested & GDK_FRAME_CLOCK_PHASE_UPDATE)
248                 {
249                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_UPDATE;
250                   g_signal_emit_by_name (G_OBJECT (clock), "update");
251                 }
252             }
253         case GDK_FRAME_CLOCK_PHASE_LAYOUT:
254           if (priv->freeze_count == 0)
255             {
256               priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
257               if (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT)
258                 {
259                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;
260                   g_signal_emit_by_name (G_OBJECT (clock), "layout");
261                 }
262             }
263         case GDK_FRAME_CLOCK_PHASE_PAINT:
264           if (priv->freeze_count == 0)
265             {
266               priv->phase = GDK_FRAME_CLOCK_PHASE_PAINT;
267               if (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT)
268                 {
269                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
270                   g_signal_emit_by_name (G_OBJECT (clock), "paint");
271                 }
272             }
273         case GDK_FRAME_CLOCK_PHASE_AFTER_PAINT:
274           if (priv->freeze_count == 0)
275             {
276               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT;
277               g_signal_emit_by_name (G_OBJECT (clock), "after-paint");
278               /* the ::after-paint phase doesn't get repeated on freeze/thaw,
279                */
280               priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
281             }
282         case GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS:
283           ;
284         }
285     }
286
287   if (priv->requested & GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)
288     {
289       priv->requested &= ~GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS;
290       g_signal_emit_by_name (G_OBJECT (clock), "resume-events");
291     }
292
293   priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
294
295   if (priv->freeze_count == 0 && priv->requested != 0)
296     {
297       /* We need to start over again immediately - this implies that there is no
298        * throttling at the backend layer, so we need to back-off ourselves.
299        */
300       gdk_flush ();
301       priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL;
302       maybe_start_idle (clock_idle);
303     }
304   else
305     {
306       priv->min_next_frame_time = 0;
307     }
308
309   return FALSE;
310 }
311
312 static void
313 gdk_frame_clock_idle_request_phase (GdkFrameClock      *clock,
314                                     GdkFrameClockPhase  phase)
315 {
316   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
317   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
318
319   priv->requested |= phase;
320   maybe_start_idle (clock_idle);
321 }
322
323 static GdkFrameClockPhase
324 gdk_frame_clock_idle_get_requested (GdkFrameClock *clock)
325 {
326   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
327
328   return priv->requested;
329 }
330
331 static void
332 gdk_frame_clock_idle_freeze (GdkFrameClock *clock)
333 {
334   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
335
336   priv->freeze_count++;
337
338   if (priv->freeze_count == 1)
339     {
340       if (priv->flush_idle_id)
341         {
342           g_source_remove (priv->flush_idle_id);
343           priv->flush_idle_id = 0;
344         }
345       if (priv->paint_idle_id)
346         {
347           g_source_remove (priv->paint_idle_id);
348           priv->paint_idle_id = 0;
349         }
350     }
351 }
352
353 static void
354 gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
355 {
356   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
357   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
358
359   g_return_if_fail (priv->freeze_count > 0);
360
361   priv->freeze_count--;
362   if (priv->freeze_count == 0)
363     {
364       maybe_start_idle (clock_idle);
365       /* If nothing is requested so we didn't start an idle, we need
366        * to skip to the end of the state chain, since the idle won't
367        * run and do it for us. */
368       if (priv->paint_idle_id == 0)
369         priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
370     }
371 }
372
373 static void
374 gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
375 {
376   iface->get_frame_time = gdk_frame_clock_idle_get_frame_time;
377   iface->request_phase = gdk_frame_clock_idle_request_phase;
378   iface->get_requested = gdk_frame_clock_idle_get_requested;
379   iface->freeze = gdk_frame_clock_idle_freeze;
380   iface->thaw = gdk_frame_clock_idle_thaw;
381 }
382
383 GdkFrameClock *
384 _gdk_frame_clock_idle_new (void)
385 {
386   GdkFrameClockIdle *clock;
387
388   clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
389
390   return GDK_FRAME_CLOCK (clock);
391 }