]> Pileus Git - ~andy/gtk/blob - gdk/gdkframeclockidle.c
Add GdkFrameHistory and GdkFrameTimings, handle _NET_WM_FRAME_TIMINGS
[~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   GdkFrameHistory *history;
37   GTimer *timer;
38   /* timer_base is used to avoid ever going backward */
39   guint64 timer_base;
40   guint64 frame_time;
41   guint64 min_next_frame_time;
42
43   guint flush_idle_id;
44   guint paint_idle_id;
45   guint freeze_count;
46
47   GdkFrameClockPhase requested;
48   GdkFrameClockPhase phase;
49 };
50
51 static gboolean gdk_frame_clock_flush_idle (void *data);
52 static gboolean gdk_frame_clock_paint_idle (void *data);
53
54 static void gdk_frame_clock_idle_finalize             (GObject                *object);
55 static void gdk_frame_clock_idle_interface_init       (GdkFrameClockInterface *iface);
56
57 G_DEFINE_TYPE_WITH_CODE (GdkFrameClockIdle, gdk_frame_clock_idle, G_TYPE_OBJECT,
58                          G_IMPLEMENT_INTERFACE (GDK_TYPE_FRAME_CLOCK,
59                                                 gdk_frame_clock_idle_interface_init))
60
61 static void
62 gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
63 {
64   GObjectClass *gobject_class = (GObjectClass*) klass;
65
66   gobject_class->finalize     = gdk_frame_clock_idle_finalize;
67
68   g_type_class_add_private (klass, sizeof (GdkFrameClockIdlePrivate));
69 }
70
71 static void
72 gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
73 {
74   GdkFrameClockIdlePrivate *priv;
75
76   frame_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (frame_clock_idle,
77                                                         GDK_TYPE_FRAME_CLOCK_IDLE,
78                                                         GdkFrameClockIdlePrivate);
79   priv = frame_clock_idle->priv;
80
81   priv->history = gdk_frame_history_new ();
82   priv->timer = g_timer_new ();
83   priv->freeze_count = 0;
84 }
85
86 static void
87 gdk_frame_clock_idle_finalize (GObject *object)
88 {
89   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (object)->priv;
90
91   g_timer_destroy (priv->timer);
92
93   G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->finalize (object);
94 }
95
96 static guint64
97 compute_frame_time (GdkFrameClockIdle *idle)
98 {
99   GdkFrameClockIdlePrivate *priv = idle->priv;
100   guint64 computed_frame_time;
101   guint64 elapsed;
102
103   elapsed = g_get_monotonic_time () + priv->timer_base;
104   if (elapsed < priv->frame_time)
105     {
106       /* clock went backward. adapt to that by forevermore increasing
107        * timer_base.  For now, assume we've gone forward in time 1ms.
108        */
109       /* hmm. just fix GTimer? */
110       computed_frame_time = priv->frame_time + 1;
111       priv->timer_base += (priv->frame_time - elapsed) + 1;
112     }
113   else
114     {
115       computed_frame_time = elapsed;
116     }
117
118   return computed_frame_time;
119 }
120
121 static guint64
122 gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock)
123 {
124   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
125   guint64 computed_frame_time;
126
127   /* can't change frame time during a paint */
128   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE &&
129       priv->phase != GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS)
130     return priv->frame_time;
131
132   /* Outside a paint, pick something close to "now" */
133   computed_frame_time = compute_frame_time (GDK_FRAME_CLOCK_IDLE (clock));
134
135   /* 16ms is 60fps. We only update frame time that often because we'd
136    * like to try to keep animations on the same start times.
137    * get_frame_time() would normally be used outside of a paint to
138    * record an animation start time for example.
139    */
140   if ((computed_frame_time - priv->frame_time) > FRAME_INTERVAL)
141     priv->frame_time = computed_frame_time;
142
143   return priv->frame_time;
144 }
145
146 static void
147 maybe_start_idle (GdkFrameClockIdle *clock_idle)
148 {
149   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
150
151   if (priv->freeze_count == 0)
152     {
153       guint min_interval = 0;
154
155       if (priv->min_next_frame_time != 0)
156         {
157           guint64 now = compute_frame_time (clock_idle);
158           guint64 min_interval_us = MAX (priv->min_next_frame_time, now) - now;
159           min_interval = (min_interval_us + 500) / 1000;
160         }
161
162       if (priv->flush_idle_id == 0 &&
163           (priv->requested & GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
164         {
165           priv->flush_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS + 1,
166                                                               min_interval,
167                                                               gdk_frame_clock_flush_idle,
168                                                               g_object_ref (clock_idle),
169                                                               (GDestroyNotify) g_object_unref);
170         }
171
172       if (priv->paint_idle_id == 0 &&
173           (priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
174         {
175           priv->paint_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_REDRAW,
176                                                               min_interval,
177                                                               gdk_frame_clock_paint_idle,
178                                                               g_object_ref (clock_idle),
179                                                               (GDestroyNotify) g_object_unref);
180
181           gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle));
182         }
183     }
184 }
185
186 static gboolean
187 gdk_frame_clock_flush_idle (void *data)
188 {
189   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
190   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
191   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
192
193   priv->flush_idle_id = 0;
194
195   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE)
196     return FALSE;
197
198   priv->phase = GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
199   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
200
201   g_signal_emit_by_name (G_OBJECT (clock), "flush-events");
202
203   if ((priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
204     priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
205   else
206     priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
207
208   return FALSE;
209 }
210
211 static gboolean
212 gdk_frame_clock_paint_idle (void *data)
213 {
214   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
215   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
216   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
217   gboolean skip_to_resume_events;
218
219   priv->paint_idle_id = 0;
220
221   skip_to_resume_events =
222     (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
223
224   if (!skip_to_resume_events)
225     {
226       switch (priv->phase)
227         {
228         case GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS:
229           break;
230         case GDK_FRAME_CLOCK_PHASE_NONE:
231         case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT:
232           if (priv->freeze_count == 0)
233             {
234               GdkFrameTimings *timings;
235               gint64 frame_counter;
236
237               priv->frame_time = compute_frame_time (clock_idle);
238               gdk_frame_history_begin_frame (priv->history);
239               frame_counter = gdk_frame_history_get_frame_counter (priv->history);
240               timings = gdk_frame_history_get_timings (priv->history, frame_counter);
241               gdk_frame_timings_set_frame_time (timings, priv->frame_time);
242
243               priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
244
245               /* We always emit ::before-paint and ::after-paint if
246                * any of the intermediate phases are requested and
247                * they don't get repeated if you freeze/thaw while
248                * in them. */
249               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
250               g_signal_emit_by_name (G_OBJECT (clock), "before-paint");
251               priv->phase = GDK_FRAME_CLOCK_PHASE_UPDATE;
252             }
253         case GDK_FRAME_CLOCK_PHASE_UPDATE:
254           if (priv->freeze_count == 0)
255             {
256               if (priv->requested & GDK_FRAME_CLOCK_PHASE_UPDATE)
257                 {
258                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_UPDATE;
259                   g_signal_emit_by_name (G_OBJECT (clock), "update");
260                 }
261             }
262         case GDK_FRAME_CLOCK_PHASE_LAYOUT:
263           if (priv->freeze_count == 0)
264             {
265               priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
266               if (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT)
267                 {
268                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;
269                   g_signal_emit_by_name (G_OBJECT (clock), "layout");
270                 }
271             }
272         case GDK_FRAME_CLOCK_PHASE_PAINT:
273           if (priv->freeze_count == 0)
274             {
275               priv->phase = GDK_FRAME_CLOCK_PHASE_PAINT;
276               if (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT)
277                 {
278                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
279                   g_signal_emit_by_name (G_OBJECT (clock), "paint");
280                 }
281             }
282         case GDK_FRAME_CLOCK_PHASE_AFTER_PAINT:
283           if (priv->freeze_count == 0)
284             {
285               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT;
286               g_signal_emit_by_name (G_OBJECT (clock), "after-paint");
287               /* the ::after-paint phase doesn't get repeated on freeze/thaw,
288                */
289               priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
290             }
291         case GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS:
292           ;
293         }
294     }
295
296   if (priv->requested & GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)
297     {
298       priv->requested &= ~GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS;
299       g_signal_emit_by_name (G_OBJECT (clock), "resume-events");
300     }
301
302   priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
303
304   if (priv->freeze_count == 0 && priv->requested != 0)
305     {
306       /* We need to start over again immediately - this implies that there is no
307        * throttling at the backend layer, so we need to back-off ourselves.
308        */
309       gdk_flush ();
310       priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL;
311       maybe_start_idle (clock_idle);
312     }
313   else
314     {
315       priv->min_next_frame_time = 0;
316     }
317
318   return FALSE;
319 }
320
321 static void
322 gdk_frame_clock_idle_request_phase (GdkFrameClock      *clock,
323                                     GdkFrameClockPhase  phase)
324 {
325   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
326   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
327
328   priv->requested |= phase;
329   maybe_start_idle (clock_idle);
330 }
331
332 static GdkFrameClockPhase
333 gdk_frame_clock_idle_get_requested (GdkFrameClock *clock)
334 {
335   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
336
337   return priv->requested;
338 }
339
340 static void
341 gdk_frame_clock_idle_freeze (GdkFrameClock *clock)
342 {
343   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
344
345   priv->freeze_count++;
346
347   if (priv->freeze_count == 1)
348     {
349       if (priv->flush_idle_id)
350         {
351           g_source_remove (priv->flush_idle_id);
352           priv->flush_idle_id = 0;
353         }
354       if (priv->paint_idle_id)
355         {
356           g_source_remove (priv->paint_idle_id);
357           priv->paint_idle_id = 0;
358         }
359     }
360 }
361
362 static void
363 gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
364 {
365   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
366   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
367
368   g_return_if_fail (priv->freeze_count > 0);
369
370   priv->freeze_count--;
371   if (priv->freeze_count == 0)
372     {
373       maybe_start_idle (clock_idle);
374       /* If nothing is requested so we didn't start an idle, we need
375        * to skip to the end of the state chain, since the idle won't
376        * run and do it for us. */
377       if (priv->paint_idle_id == 0)
378         priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
379     }
380 }
381
382 static GdkFrameHistory *
383 gdk_frame_clock_idle_get_history (GdkFrameClock *clock)
384 {
385   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
386   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
387
388   return priv->history;
389 }
390
391 static void
392 gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
393 {
394   iface->get_frame_time = gdk_frame_clock_idle_get_frame_time;
395   iface->request_phase = gdk_frame_clock_idle_request_phase;
396   iface->get_requested = gdk_frame_clock_idle_get_requested;
397   iface->freeze = gdk_frame_clock_idle_freeze;
398   iface->thaw = gdk_frame_clock_idle_thaw;
399   iface->get_history = gdk_frame_clock_idle_get_history;
400 }
401
402 GdkFrameClock *
403 _gdk_frame_clock_idle_new (void)
404 {
405   GdkFrameClockIdle *clock;
406
407   clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
408
409   return GDK_FRAME_CLOCK (clock);
410 }