]> Pileus Git - ~andy/gtk/blob - gdk/gdkframeclock.c
GdkFrameClock: Clean up the public API
[~andy/gtk] / gdk / gdkframeclock.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 "gdkframeclockprivate.h"
30 #include "gdkinternals.h"
31
32 /**
33  * SECTION:frameclock
34  * @Short_description: Frame clock syncs painting to a window or display
35  * @Title: Frame clock
36  *
37  * A #GdkFrameClock tells the application when to repaint a window.
38  * This may be synced to the vertical refresh rate of the monitor, for
39  * example. Even when the frame clock uses a simple timer rather than
40  * a hardware-based vertical sync, the frame clock helps because it
41  * ensures everything paints at the same time (reducing the total
42  * number of frames). The frame clock can also automatically stop
43  * painting when it knows the frames will not be visible, or scale back
44  * animation framerates.
45  *
46  * #GdkFrameClock is designed to be compatible with an OpenGL-based
47  * implementation or with mozRequestAnimationFrame in Firefox,
48  * for example.
49  *
50  * A frame clock is idle until someone requests a frame with
51  * gdk_frame_clock_request_phase(). At that time, the frame clock
52  * emits its GdkFrameClock:frame-requested signal if no frame was
53  * already pending.
54  *
55  * At some later time after the frame is requested, the frame clock
56  * MAY indicate that a frame should be painted. To paint a frame the
57  * clock will: Emit GdkFrameClock:before-paint; update the frame time
58  * in the default handler for GdkFrameClock:before-paint; emit
59  * GdkFrameClock:paint; emit GdkFrameClock:after-paint.  The app
60  * should paint in a handler for the paint signal.
61  *
62  * If a given frame is not painted (the clock is idle), the frame time
63  * should still update to a conceptual "last frame." i.e. the frame
64  * time will keep moving forward roughly with wall clock time.
65  *
66  * The frame time is in milliseconds. However, it should not be
67  * thought of as having any particular relationship to wall clock
68  * time. Unlike wall clock time, it "snaps" to conceptual frame times
69  * so is low-resolution; it is guaranteed to never move backward (so
70  * say you reset your computer clock, the frame clock will not reset);
71  * and the frame clock is allowed to drift. For example nicer
72  * results when painting with vertical refresh sync may be obtained by
73  * painting as rapidly as possible, but always incrementing the frame
74  * time by the frame length on each frame. This results in a frame
75  * time that doesn't have a lot to do with wall clock time.
76  */
77
78 G_DEFINE_ABSTRACT_TYPE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT)
79
80 enum {
81   FLUSH_EVENTS,
82   BEFORE_PAINT,
83   UPDATE,
84   LAYOUT,
85   PAINT,
86   AFTER_PAINT,
87   RESUME_EVENTS,
88   LAST_SIGNAL
89 };
90
91 static guint signals[LAST_SIGNAL];
92
93 #define FRAME_HISTORY_MAX_LENGTH 16
94
95 struct _GdkFrameClockPrivate
96 {
97   gint64 frame_counter;
98   gint n_timings;
99   gint current;
100   GdkFrameTimings *timings[FRAME_HISTORY_MAX_LENGTH];
101 };
102
103 static void
104 gdk_frame_clock_finalize (GObject *object)
105 {
106   GdkFrameClockPrivate *priv = GDK_FRAME_CLOCK (object)->priv;
107   int i;
108
109   for (i = 0; i < FRAME_HISTORY_MAX_LENGTH; i++)
110     if (priv->timings[i] != 0)
111       gdk_frame_timings_unref (priv->timings[i]);
112
113   G_OBJECT_CLASS (gdk_frame_clock_parent_class)->finalize (object);
114 }
115
116 static void
117 gdk_frame_clock_class_init (GdkFrameClockClass *klass)
118 {
119   GObjectClass *gobject_class = (GObjectClass*) klass;
120
121   gobject_class->finalize     = gdk_frame_clock_finalize;
122
123   /**
124    * GdkFrameClock::flush-events:
125    * @clock: the frame clock emitting the signal
126    *
127    * FIXME.
128    */
129   signals[FLUSH_EVENTS] =
130     g_signal_new (g_intern_static_string ("flush-events"),
131                   GDK_TYPE_FRAME_CLOCK,
132                   G_SIGNAL_RUN_LAST,
133                   0,
134                   NULL, NULL,
135                   g_cclosure_marshal_VOID__VOID,
136                   G_TYPE_NONE, 0);
137
138   /**
139    * GdkFrameClock::before-paint:
140    * @clock: the frame clock emitting the signal
141    *
142    * This signal is emitted immediately before the paint signal and
143    * indicates that the frame time has been updated, and signal
144    * handlers should perform any preparatory work before painting.
145    */
146   signals[BEFORE_PAINT] =
147     g_signal_new (g_intern_static_string ("before-paint"),
148                   GDK_TYPE_FRAME_CLOCK,
149                   G_SIGNAL_RUN_LAST,
150                   0,
151                   NULL, NULL,
152                   g_cclosure_marshal_VOID__VOID,
153                   G_TYPE_NONE, 0);
154
155   /**
156    * GdkFrameClock::update:
157    * @clock: the frame clock emitting the signal
158    *
159    * FIXME.
160    */
161   signals[UPDATE] =
162     g_signal_new (g_intern_static_string ("update"),
163                   GDK_TYPE_FRAME_CLOCK,
164                   G_SIGNAL_RUN_LAST,
165                   0,
166                   NULL, NULL,
167                   g_cclosure_marshal_VOID__VOID,
168                   G_TYPE_NONE, 0);
169
170   /**
171    * GdkFrameClock::layout:
172    * @clock: the frame clock emitting the signal
173    *
174    * This signal is emitted immediately before the paint signal and
175    * indicates that the frame time has been updated, and signal
176    * handlers should perform any preparatory work before painting.
177    */
178   signals[LAYOUT] =
179     g_signal_new (g_intern_static_string ("layout"),
180                   GDK_TYPE_FRAME_CLOCK,
181                   G_SIGNAL_RUN_LAST,
182                   0,
183                   NULL, NULL,
184                   g_cclosure_marshal_VOID__VOID,
185                   G_TYPE_NONE, 0);
186
187   /**
188    * GdkFrameClock::paint:
189    * @clock: the frame clock emitting the signal
190    *
191    * Signal handlers for this signal should paint the window, screen,
192    * or whatever they normally paint.
193    */
194   signals[PAINT] =
195     g_signal_new (g_intern_static_string ("paint"),
196                   GDK_TYPE_FRAME_CLOCK,
197                   G_SIGNAL_RUN_LAST,
198                   0,
199                   NULL, NULL,
200                   g_cclosure_marshal_VOID__VOID,
201                   G_TYPE_NONE, 0);
202
203   /**
204    * GdkFrameClock::after-paint:
205    * @clock: the frame clock emitting the signal
206    *
207    * This signal is emitted immediately after the paint signal and
208    * allows signal handlers to do anything they'd like to do after
209    * painting has been completed. This is a relatively good time to do
210    * "expensive" processing in order to get it done in between frames.
211    */
212   signals[AFTER_PAINT] =
213     g_signal_new (g_intern_static_string ("after-paint"),
214                   GDK_TYPE_FRAME_CLOCK,
215                   G_SIGNAL_RUN_LAST,
216                   0,
217                   NULL, NULL,
218                   g_cclosure_marshal_VOID__VOID,
219                   G_TYPE_NONE, 0);
220
221   /**
222    * GdkFrameClock::resume-events:
223    * @clock: the frame clock emitting the signal
224    *
225    * FIXME.
226    */
227   signals[RESUME_EVENTS] =
228     g_signal_new (g_intern_static_string ("resume-events"),
229                   GDK_TYPE_FRAME_CLOCK,
230                   G_SIGNAL_RUN_LAST,
231                   0,
232                   NULL, NULL,
233                   g_cclosure_marshal_VOID__VOID,
234                   G_TYPE_NONE, 0);
235
236   g_type_class_add_private (klass, sizeof (GdkFrameClockPrivate));
237 }
238
239 static void
240 gdk_frame_clock_init (GdkFrameClock *clock)
241 {
242   GdkFrameClockPrivate *priv;
243
244   clock->priv = G_TYPE_INSTANCE_GET_PRIVATE (clock,
245                                              GDK_TYPE_FRAME_CLOCK,
246                                              GdkFrameClockPrivate);
247   priv = clock->priv;
248
249   priv->frame_counter = -1;
250   priv->current = FRAME_HISTORY_MAX_LENGTH - 1;
251 }
252
253 /**
254  * gdk_frame_clock_get_frame_time:
255  * @clock: the clock
256  *
257  * Gets the time that should currently be used for animations.  Inside
258  * a paint, it's the time used to compute the animation position of
259  * everything in a frame. Outside a paint, it's the time of the
260  * conceptual "previous frame," which may be either the actual
261  * previous frame time, or if that's too old, an updated time.
262  *
263  * The returned time has no relationship to wall clock time.  It
264  * increases roughly at 1 millisecond per wall clock millisecond, and
265  * it never decreases, but its value is only meaningful relative to
266  * previous frame clock times.
267  *
268  *
269  * Since: 3.0
270  * Return value: a timestamp in milliseconds
271  */
272 guint64
273 gdk_frame_clock_get_frame_time (GdkFrameClock *clock)
274 {
275   g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
276
277   return GDK_FRAME_CLOCK_GET_CLASS (clock)->get_frame_time (clock);
278 }
279
280 /**
281  * gdk_frame_clock_request_phase:
282  * @clock: the clock
283  *
284  * Asks the frame clock to paint a frame. The frame
285  * may or may not ever be painted (the frame clock may
286  * stop itself for whatever reason), but the goal in
287  * normal circumstances would be to paint the frame
288  * at the next expected frame time. For example
289  * if the clock is running at 60fps the frame would
290  * ideally be painted within 1000/60=16 milliseconds.
291  *
292  * Since: 3.0
293  */
294 void
295 gdk_frame_clock_request_phase (GdkFrameClock      *clock,
296                                GdkFrameClockPhase  phase)
297 {
298   g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
299
300   GDK_FRAME_CLOCK_GET_CLASS (clock)->request_phase (clock, phase);
301 }
302
303
304 void
305 _gdk_frame_clock_freeze (GdkFrameClock *clock)
306 {
307   g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
308
309   GDK_FRAME_CLOCK_GET_CLASS (clock)->freeze (clock);
310 }
311
312
313 void
314 _gdk_frame_clock_thaw (GdkFrameClock *clock)
315 {
316   g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
317
318   GDK_FRAME_CLOCK_GET_CLASS (clock)->thaw (clock);
319 }
320
321 gint64
322 gdk_frame_clock_get_frame_counter (GdkFrameClock *clock)
323 {
324   GdkFrameClockPrivate *priv;
325
326   g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
327
328   priv = clock->priv;
329
330   return priv->frame_counter;
331 }
332
333 gint64
334 gdk_frame_clock_get_history_start (GdkFrameClock *clock)
335 {
336   GdkFrameClockPrivate *priv;
337
338   g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
339
340   priv = clock->priv;
341
342   return priv->frame_counter + 1 - priv->n_timings;
343 }
344
345 void
346 _gdk_frame_clock_begin_frame (GdkFrameClock *clock)
347 {
348   GdkFrameClockPrivate *priv;
349
350   g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
351
352   priv = clock->priv;
353
354   priv->frame_counter++;
355   priv->current = (priv->current + 1) % FRAME_HISTORY_MAX_LENGTH;
356
357   if (priv->n_timings < FRAME_HISTORY_MAX_LENGTH)
358     priv->n_timings++;
359   else
360     {
361       gdk_frame_timings_unref(priv->timings[priv->current]);
362     }
363
364   priv->timings[priv->current] = _gdk_frame_timings_new (priv->frame_counter);
365 }
366
367 GdkFrameTimings *
368 gdk_frame_clock_get_timings (GdkFrameClock *clock,
369                              gint64         frame_counter)
370 {
371   GdkFrameClockPrivate *priv;
372   gint pos;
373
374   g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), NULL);
375
376   priv = clock->priv;
377
378   if (frame_counter > priv->frame_counter)
379     return NULL;
380
381   if (frame_counter <= priv->frame_counter - priv->n_timings)
382     return NULL;
383
384   pos = (priv->current - (priv->frame_counter - frame_counter) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
385
386   return priv->timings[pos];
387 }
388
389 GdkFrameTimings *
390 gdk_frame_clock_get_frame_timings (GdkFrameClock *clock)
391 {
392   GdkFrameClockPrivate *priv;
393
394   g_return_val_if_fail (GDK_IS_FRAME_CLOCK (clock), 0);
395
396   priv = clock->priv;
397
398   return gdk_frame_clock_get_timings (clock, priv->frame_counter);
399 }
400
401
402 #ifdef G_ENABLE_DEBUG
403 void
404 _gdk_frame_clock_debug_print_timings (GdkFrameClock   *clock,
405                                       GdkFrameTimings *timings)
406 {
407   gint64 previous_frame_time = 0;
408   GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (clock,
409                                                                    timings->frame_counter - 1);
410
411   if (previous_timings != NULL)
412     previous_frame_time = previous_timings->frame_time;
413
414   g_print ("%5" G_GINT64_FORMAT ":", timings->frame_counter);
415   if (previous_frame_time != 0)
416     {
417       g_print (" interval=%-4.1f", (timings->frame_time - previous_frame_time) / 1000.);
418       g_print (timings->slept_before ?  " (sleep)" : "        ");
419     }
420   if (timings->layout_start_time != 0)
421     g_print (" layout_start=%-4.1f", (timings->layout_start_time - timings->frame_time) / 1000.);
422   if (timings->paint_start_time != 0)
423     g_print (" paint_start=%-4.1f", (timings->paint_start_time - timings->frame_time) / 1000.);
424   if (timings->frame_end_time != 0)
425     g_print (" frame_end=%-4.1f", (timings->frame_end_time - timings->frame_time) / 1000.);
426   if (timings->presentation_time != 0)
427     g_print (" present=%-4.1f", (timings->presentation_time - timings->frame_time) / 1000.);
428   if (timings->predicted_presentation_time != 0)
429     g_print (" predicted=%-4.1f", (timings->predicted_presentation_time - timings->frame_time) / 1000.);
430   if (timings->refresh_interval != 0)
431     g_print (" refresh_interval=%-4.1f", timings->refresh_interval / 1000.);
432   g_print ("\n");
433 }
434 #endif /* G_ENABLE_DEBUG */
435
436 #define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
437 #define MAX_HISTORY_AGE 150000         /* 150ms */
438
439 void
440 gdk_frame_clock_get_refresh_info (GdkFrameClock *clock,
441                                   gint64         base_time,
442                                   gint64        *refresh_interval_return,
443                                   gint64        *presentation_time_return)
444 {
445   gint64 frame_counter;
446
447   g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
448
449   frame_counter = gdk_frame_clock_get_frame_counter (clock);
450
451   if (presentation_time_return)
452     *presentation_time_return = 0;
453   if (refresh_interval_return)
454     *refresh_interval_return = DEFAULT_REFRESH_INTERVAL;
455
456   while (TRUE)
457     {
458       GdkFrameTimings *timings = gdk_frame_clock_get_timings (clock, frame_counter);
459       gint64 presentation_time;
460       gint64 refresh_interval;
461
462       if (timings == NULL)
463         return;
464
465       refresh_interval = timings->refresh_interval;
466       presentation_time = timings->presentation_time;
467
468       if (presentation_time != 0)
469         {
470           if (presentation_time > base_time - MAX_HISTORY_AGE &&
471               presentation_time_return)
472             {
473               if (refresh_interval == 0)
474                 refresh_interval = DEFAULT_REFRESH_INTERVAL;
475
476               if (refresh_interval_return)
477                 *refresh_interval_return = refresh_interval;
478
479               while (presentation_time < base_time)
480                 presentation_time += refresh_interval;
481
482               if (presentation_time_return)
483                 *presentation_time_return = presentation_time;
484             }
485
486           return;
487         }
488
489       frame_counter--;
490     }
491 }