]> Pileus Git - ~andy/gtk/blob - gdk/gdkframeclockidle.c
Add GDK_DEBUG=frames
[~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 "gdkinternals.h"
30 #include "gdkframeclockidle.h"
31 #include "gdk.h"
32
33 #define FRAME_INTERVAL 16667 // microseconds
34
35 struct _GdkFrameClockIdlePrivate
36 {
37   GdkFrameHistory *history;
38   GTimer *timer;
39   /* timer_base is used to avoid ever going backward */
40   guint64 timer_base;
41   guint64 frame_time;
42   guint64 min_next_frame_time;
43   gint64 sleep_serial;
44
45   guint flush_idle_id;
46   guint paint_idle_id;
47   guint freeze_count;
48
49   GdkFrameClockPhase requested;
50   GdkFrameClockPhase phase;
51
52   guint in_paint_idle : 1;
53 };
54
55 static gboolean gdk_frame_clock_flush_idle (void *data);
56 static gboolean gdk_frame_clock_paint_idle (void *data);
57
58 static void gdk_frame_clock_idle_finalize             (GObject                *object);
59 static void gdk_frame_clock_idle_interface_init       (GdkFrameClockInterface *iface);
60
61 G_DEFINE_TYPE_WITH_CODE (GdkFrameClockIdle, gdk_frame_clock_idle, G_TYPE_OBJECT,
62                          G_IMPLEMENT_INTERFACE (GDK_TYPE_FRAME_CLOCK,
63                                                 gdk_frame_clock_idle_interface_init))
64
65 static gint64 sleep_serial;
66 static gint64 sleep_source_prepare_time;
67 static GSource *sleep_source;
68
69 gboolean
70 sleep_source_prepare (GSource *source,
71                       gint    *timeout)
72 {
73   sleep_source_prepare_time = g_source_get_time (source);
74   *timeout = -1;
75   return FALSE;
76 }
77
78 gboolean
79 sleep_source_check (GSource *source)
80 {
81   if (g_source_get_time (source) != sleep_source_prepare_time)
82     sleep_serial++;
83
84   return FALSE;
85 }
86
87 gboolean
88 sleep_source_dispatch (GSource     *source,
89                        GSourceFunc  callback,
90                        gpointer     user_data)
91 {
92   return TRUE;
93 }
94
95 static GSourceFuncs sleep_source_funcs = {
96   sleep_source_prepare,
97   sleep_source_check,
98   sleep_source_dispatch,
99   NULL /* finalize */
100 };
101
102 static gint64
103 get_sleep_serial (void)
104 {
105   if (sleep_source == NULL)
106     {
107       sleep_source = g_source_new (&sleep_source_funcs, sizeof (GSource));
108
109       g_source_set_priority (sleep_source, G_PRIORITY_HIGH);
110       g_source_attach (sleep_source, NULL);
111       g_source_unref (sleep_source);
112     }
113
114   return sleep_serial;
115 }
116
117 static void
118 gdk_frame_clock_idle_class_init (GdkFrameClockIdleClass *klass)
119 {
120   GObjectClass *gobject_class = (GObjectClass*) klass;
121
122   gobject_class->finalize     = gdk_frame_clock_idle_finalize;
123
124   g_type_class_add_private (klass, sizeof (GdkFrameClockIdlePrivate));
125 }
126
127 static void
128 gdk_frame_clock_idle_init (GdkFrameClockIdle *frame_clock_idle)
129 {
130   GdkFrameClockIdlePrivate *priv;
131
132   frame_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (frame_clock_idle,
133                                                         GDK_TYPE_FRAME_CLOCK_IDLE,
134                                                         GdkFrameClockIdlePrivate);
135   priv = frame_clock_idle->priv;
136
137   priv->history = gdk_frame_history_new ();
138   priv->timer = g_timer_new ();
139   priv->freeze_count = 0;
140 }
141
142 static void
143 gdk_frame_clock_idle_finalize (GObject *object)
144 {
145   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (object)->priv;
146
147   g_timer_destroy (priv->timer);
148
149   G_OBJECT_CLASS (gdk_frame_clock_idle_parent_class)->finalize (object);
150 }
151
152 static guint64
153 compute_frame_time (GdkFrameClockIdle *idle)
154 {
155   GdkFrameClockIdlePrivate *priv = idle->priv;
156   guint64 computed_frame_time;
157   guint64 elapsed;
158
159   elapsed = g_get_monotonic_time () + priv->timer_base;
160   if (elapsed < priv->frame_time)
161     {
162       /* clock went backward. adapt to that by forevermore increasing
163        * timer_base.  For now, assume we've gone forward in time 1ms.
164        */
165       /* hmm. just fix GTimer? */
166       computed_frame_time = priv->frame_time + 1;
167       priv->timer_base += (priv->frame_time - elapsed) + 1;
168     }
169   else
170     {
171       computed_frame_time = elapsed;
172     }
173
174   return computed_frame_time;
175 }
176
177 static guint64
178 gdk_frame_clock_idle_get_frame_time (GdkFrameClock *clock)
179 {
180   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
181   guint64 computed_frame_time;
182
183   /* can't change frame time during a paint */
184   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE &&
185       priv->phase != GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS)
186     return priv->frame_time;
187
188   /* Outside a paint, pick something close to "now" */
189   computed_frame_time = compute_frame_time (GDK_FRAME_CLOCK_IDLE (clock));
190
191   /* 16ms is 60fps. We only update frame time that often because we'd
192    * like to try to keep animations on the same start times.
193    * get_frame_time() would normally be used outside of a paint to
194    * record an animation start time for example.
195    */
196   if ((computed_frame_time - priv->frame_time) > FRAME_INTERVAL)
197     priv->frame_time = computed_frame_time;
198
199   return priv->frame_time;
200 }
201
202 static void
203 maybe_start_idle (GdkFrameClockIdle *clock_idle)
204 {
205   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
206
207   if (priv->freeze_count == 0)
208     {
209       guint min_interval = 0;
210
211       if (priv->min_next_frame_time != 0)
212         {
213           guint64 now = compute_frame_time (clock_idle);
214           guint64 min_interval_us = MAX (priv->min_next_frame_time, now) - now;
215           min_interval = (min_interval_us + 500) / 1000;
216         }
217
218       if (priv->flush_idle_id == 0 &&
219           (priv->requested & GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
220         {
221           priv->flush_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS + 1,
222                                                               min_interval,
223                                                               gdk_frame_clock_flush_idle,
224                                                               g_object_ref (clock_idle),
225                                                               (GDestroyNotify) g_object_unref);
226         }
227
228       if (priv->paint_idle_id == 0 &&
229           !priv->in_paint_idle &&
230           (priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
231         {
232           priv->paint_idle_id = gdk_threads_add_timeout_full (GDK_PRIORITY_REDRAW,
233                                                               min_interval,
234                                                               gdk_frame_clock_paint_idle,
235                                                               g_object_ref (clock_idle),
236                                                               (GDestroyNotify) g_object_unref);
237
238           gdk_frame_clock_frame_requested (GDK_FRAME_CLOCK (clock_idle));
239         }
240     }
241 }
242
243 static gboolean
244 gdk_frame_clock_flush_idle (void *data)
245 {
246   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
247   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
248   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
249
250   priv->flush_idle_id = 0;
251
252   if (priv->phase != GDK_FRAME_CLOCK_PHASE_NONE)
253     return FALSE;
254
255   priv->phase = GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
256   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS;
257
258   g_signal_emit_by_name (G_OBJECT (clock), "flush-events");
259
260   if ((priv->requested & ~GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS) != 0)
261     priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
262   else
263     priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
264
265   return FALSE;
266 }
267
268 static gboolean
269 gdk_frame_clock_paint_idle (void *data)
270 {
271   GdkFrameClock *clock = GDK_FRAME_CLOCK (data);
272   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
273   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
274   gboolean skip_to_resume_events;
275   GdkFrameTimings *timings = NULL;
276   gint64 frame_counter = 0;
277
278   priv->paint_idle_id = 0;
279   priv->in_paint_idle = TRUE;
280
281   skip_to_resume_events =
282     (priv->requested & ~(GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS | GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)) == 0;
283
284   if (priv->phase > GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT)
285     {
286       frame_counter = gdk_frame_history_get_frame_counter (priv->history);
287       timings = gdk_frame_history_get_timings (priv->history, frame_counter);
288     }
289
290   if (!skip_to_resume_events)
291     {
292       switch (priv->phase)
293         {
294         case GDK_FRAME_CLOCK_PHASE_FLUSH_EVENTS:
295           break;
296         case GDK_FRAME_CLOCK_PHASE_NONE:
297         case GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT:
298           if (priv->freeze_count == 0)
299             {
300               priv->frame_time = compute_frame_time (clock_idle);
301
302               gdk_frame_history_begin_frame (priv->history);
303               frame_counter = gdk_frame_history_get_frame_counter (priv->history);
304               timings = gdk_frame_history_get_timings (priv->history, frame_counter);
305
306               gdk_frame_timings_set_frame_time (timings, priv->frame_time);
307
308               gdk_frame_timings_set_slept_before (timings,
309                                                   priv->sleep_serial != get_sleep_serial ());
310
311               priv->phase = GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
312
313               /* We always emit ::before-paint and ::after-paint if
314                * any of the intermediate phases are requested and
315                * they don't get repeated if you freeze/thaw while
316                * in them. */
317               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_BEFORE_PAINT;
318               g_signal_emit_by_name (G_OBJECT (clock), "before-paint");
319               priv->phase = GDK_FRAME_CLOCK_PHASE_UPDATE;
320             }
321         case GDK_FRAME_CLOCK_PHASE_UPDATE:
322           if (priv->freeze_count == 0)
323             {
324               if (priv->requested & GDK_FRAME_CLOCK_PHASE_UPDATE)
325                 {
326                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_UPDATE;
327                   g_signal_emit_by_name (G_OBJECT (clock), "update");
328                 }
329             }
330         case GDK_FRAME_CLOCK_PHASE_LAYOUT:
331           if (priv->freeze_count == 0)
332             {
333 #ifdef G_ENABLE_DEBUG
334               if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
335                 {
336                   if (priv->phase != GDK_FRAME_CLOCK_PHASE_LAYOUT &&
337                       (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT))
338                     _gdk_frame_timings_set_layout_start_time (timings, g_get_monotonic_time ());
339                 }
340 #endif /* G_ENABLE_DEBUG */
341
342               priv->phase = GDK_FRAME_CLOCK_PHASE_LAYOUT;
343               if (priv->requested & GDK_FRAME_CLOCK_PHASE_LAYOUT)
344                 {
345                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_LAYOUT;
346                   g_signal_emit_by_name (G_OBJECT (clock), "layout");
347                 }
348             }
349         case GDK_FRAME_CLOCK_PHASE_PAINT:
350           if (priv->freeze_count == 0)
351             {
352 #ifdef G_ENABLE_DEBUG
353               if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
354                 {
355                   if (priv->phase != GDK_FRAME_CLOCK_PHASE_PAINT &&
356                       (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT))
357                     _gdk_frame_timings_set_paint_start_time (timings, g_get_monotonic_time ());
358                 }
359 #endif /* G_ENABLE_DEBUG */
360
361               priv->phase = GDK_FRAME_CLOCK_PHASE_PAINT;
362               if (priv->requested & GDK_FRAME_CLOCK_PHASE_PAINT)
363                 {
364                   priv->requested &= ~GDK_FRAME_CLOCK_PHASE_PAINT;
365                   g_signal_emit_by_name (G_OBJECT (clock), "paint");
366                 }
367             }
368         case GDK_FRAME_CLOCK_PHASE_AFTER_PAINT:
369           if (priv->freeze_count == 0)
370             {
371               priv->requested &= ~GDK_FRAME_CLOCK_PHASE_AFTER_PAINT;
372               g_signal_emit_by_name (G_OBJECT (clock), "after-paint");
373               /* the ::after-paint phase doesn't get repeated on freeze/thaw,
374                */
375               priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
376
377 #ifdef G_ENABLE_DEBUG
378               if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
379                 _gdk_frame_timings_set_frame_end_time (timings, g_get_monotonic_time ());
380 #endif /* G_ENABLE_DEBUG */
381             }
382         case GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS:
383           ;
384         }
385     }
386
387 #ifdef G_ENABLE_DEBUG
388   if ((_gdk_debug_flags & GDK_DEBUG_FRAMES) != 0)
389     {
390       if (gdk_frame_timings_get_complete (timings))
391         _gdk_frame_history_debug_print (priv->history, timings);
392     }
393 #endif /* G_ENABLE_DEBUG */
394
395   if (priv->requested & GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS)
396     {
397       priv->requested &= ~GDK_FRAME_CLOCK_PHASE_RESUME_EVENTS;
398       g_signal_emit_by_name (G_OBJECT (clock), "resume-events");
399     }
400
401   if (priv->freeze_count == 0)
402     priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
403
404   priv->in_paint_idle = FALSE;
405
406   if (priv->freeze_count == 0 && priv->requested != 0)
407     {
408       /* We need to start over again immediately - this implies that there is no
409        * throttling at the backend layer, so we need to back-off ourselves.
410        */
411       gdk_flush ();
412       priv->min_next_frame_time = priv->frame_time + FRAME_INTERVAL;
413       maybe_start_idle (clock_idle);
414     }
415   else
416     {
417       priv->min_next_frame_time = 0;
418     }
419
420   if (priv->freeze_count == 0)
421     priv->sleep_serial = get_sleep_serial ();
422
423   return FALSE;
424 }
425
426 static void
427 gdk_frame_clock_idle_request_phase (GdkFrameClock      *clock,
428                                     GdkFrameClockPhase  phase)
429 {
430   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
431   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
432
433   priv->requested |= phase;
434   maybe_start_idle (clock_idle);
435 }
436
437 static GdkFrameClockPhase
438 gdk_frame_clock_idle_get_requested (GdkFrameClock *clock)
439 {
440   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
441
442   return priv->requested;
443 }
444
445 static void
446 gdk_frame_clock_idle_freeze (GdkFrameClock *clock)
447 {
448   GdkFrameClockIdlePrivate *priv = GDK_FRAME_CLOCK_IDLE (clock)->priv;
449
450   priv->freeze_count++;
451
452   if (priv->freeze_count == 1)
453     {
454       if (priv->flush_idle_id)
455         {
456           g_source_remove (priv->flush_idle_id);
457           priv->flush_idle_id = 0;
458         }
459       if (priv->paint_idle_id)
460         {
461           g_source_remove (priv->paint_idle_id);
462           priv->paint_idle_id = 0;
463         }
464     }
465 }
466
467 static void
468 gdk_frame_clock_idle_thaw (GdkFrameClock *clock)
469 {
470   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
471   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
472
473   g_return_if_fail (priv->freeze_count > 0);
474
475   priv->freeze_count--;
476   if (priv->freeze_count == 0)
477     {
478       maybe_start_idle (clock_idle);
479       /* If nothing is requested so we didn't start an idle, we need
480        * to skip to the end of the state chain, since the idle won't
481        * run and do it for us. */
482       if (priv->paint_idle_id == 0)
483         priv->phase = GDK_FRAME_CLOCK_PHASE_NONE;
484
485       priv->sleep_serial = get_sleep_serial ();
486     }
487 }
488
489 static GdkFrameHistory *
490 gdk_frame_clock_idle_get_history (GdkFrameClock *clock)
491 {
492   GdkFrameClockIdle *clock_idle = GDK_FRAME_CLOCK_IDLE (clock);
493   GdkFrameClockIdlePrivate *priv = clock_idle->priv;
494
495   return priv->history;
496 }
497
498 static void
499 gdk_frame_clock_idle_interface_init (GdkFrameClockInterface *iface)
500 {
501   iface->get_frame_time = gdk_frame_clock_idle_get_frame_time;
502   iface->request_phase = gdk_frame_clock_idle_request_phase;
503   iface->get_requested = gdk_frame_clock_idle_get_requested;
504   iface->freeze = gdk_frame_clock_idle_freeze;
505   iface->thaw = gdk_frame_clock_idle_thaw;
506   iface->get_history = gdk_frame_clock_idle_get_history;
507 }
508
509 GdkFrameClock *
510 _gdk_frame_clock_idle_new (void)
511 {
512   GdkFrameClockIdle *clock;
513
514   clock = g_object_new (GDK_TYPE_FRAME_CLOCK_IDLE, NULL);
515
516   return GDK_FRAME_CLOCK (clock);
517 }