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, see <http://www.gnu.org/licenses/>.
16 */
17
18/*
19 * Modified by the GTK+ Team and others 1997-2010. See the AUTHORS
20 * file for a list of people on the GTK+ Team. See the ChangeLog
21 * files for a list of changes. These files are distributed with
22 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23 */
24
25#include "config.h"
26
27#include "gdkframeclockprivate.h"
28
29/**
30 * GdkFrameClock:
31 *
32 * A `GdkFrameClock` tells the application when to update and repaint
33 * a surface.
34 *
35 * This may be synced to the vertical refresh rate of the monitor, for example.
36 * Even when the frame clock uses a simple timer rather than a hardware-based
37 * vertical sync, the frame clock helps because it ensures everything paints at
38 * the same time (reducing the total number of frames).
39 *
40 * The frame clock can also automatically stop painting when it knows the frames
41 * will not be visible, or scale back animation framerates.
42 *
43 * `GdkFrameClock` is designed to be compatible with an OpenGL-based implementation
44 * or with mozRequestAnimationFrame in Firefox, for example.
45 *
46 * A frame clock is idle until someone requests a frame with
47 * [method@Gdk.FrameClock.request_phase]. At some later point that makes sense
48 * for the synchronization being implemented, the clock will process a frame and
49 * emit signals for each phase that has been requested. (See the signals of the
50 * `GdkFrameClock` class for documentation of the phases.
51 * %GDK_FRAME_CLOCK_PHASE_UPDATE and the [signal@GdkFrameClock::update] signal
52 * are most interesting for application writers, and are used to update the
53 * animations, using the frame time given by [method@Gdk.FrameClock.get_frame_time].
54 *
55 * The frame time is reported in microseconds and generally in the same
56 * timescale as g_get_monotonic_time(), however, it is not the same
57 * as g_get_monotonic_time(). The frame time does not advance during
58 * the time a frame is being painted, and outside of a frame, an attempt
59 * is made so that all calls to [method@Gdk.FrameClock.get_frame_time] that
60 * are called at a “similar” time get the same value. This means that
61 * if different animations are timed by looking at the difference in
62 * time between an initial value from [method@Gdk.FrameClock.get_frame_time]
63 * and the value inside the [signal@GdkFrameClock::update] signal of the clock,
64 * they will stay exactly synchronized.
65 */
66
67enum {
68 FLUSH_EVENTS,
69 BEFORE_PAINT,
70 UPDATE,
71 LAYOUT,
72 PAINT,
73 AFTER_PAINT,
74 RESUME_EVENTS,
75 LAST_SIGNAL
76};
77
78static guint signals[LAST_SIGNAL];
79
80static guint fps_counter;
81
82#define FRAME_HISTORY_MAX_LENGTH 16
83
84struct _GdkFrameClockPrivate
85{
86 gint64 frame_counter;
87 int n_timings;
88 int current;
89 GdkFrameTimings *timings[FRAME_HISTORY_MAX_LENGTH];
90 int n_freeze_inhibitors;
91};
92
93G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT)
94
95static void
96_gdk_frame_clock_freeze (GdkFrameClock *clock);
97
98static void
99gdk_frame_clock_finalize (GObject *object)
100{
101 GdkFrameClockPrivate *priv = GDK_FRAME_CLOCK (object)->priv;
102 int i;
103
104 for (i = 0; i < FRAME_HISTORY_MAX_LENGTH; i++)
105 if (priv->timings[i] != 0)
106 gdk_frame_timings_unref (timings: priv->timings[i]);
107
108 G_OBJECT_CLASS (gdk_frame_clock_parent_class)->finalize (object);
109}
110
111static void
112gdk_frame_clock_constructed (GObject *object)
113{
114 G_OBJECT_CLASS (gdk_frame_clock_parent_class)->constructed (object);
115
116 _gdk_frame_clock_freeze (GDK_FRAME_CLOCK (object));
117}
118
119static void
120gdk_frame_clock_class_init (GdkFrameClockClass *klass)
121{
122 GObjectClass *gobject_class = (GObjectClass*) klass;
123
124 gobject_class->finalize = gdk_frame_clock_finalize;
125 gobject_class->constructed = gdk_frame_clock_constructed;
126
127 /**
128 * GdkFrameClock::flush-events:
129 * @clock: the frame clock emitting the signal
130 *
131 * Used to flush pending motion events that are being batched up and
132 * compressed together.
133 *
134 * Applications should not handle this signal.
135 */
136 signals[FLUSH_EVENTS] =
137 g_signal_new (signal_name: g_intern_static_string (string: "flush-events"),
138 GDK_TYPE_FRAME_CLOCK,
139 signal_flags: G_SIGNAL_RUN_LAST,
140 class_offset: 0,
141 NULL, NULL, NULL,
142 G_TYPE_NONE, n_params: 0);
143
144 /**
145 * GdkFrameClock::before-paint:
146 * @clock: the frame clock emitting the signal
147 *
148 * Begins processing of the frame.
149 *
150 * Applications should generally not handle this signal.
151 */
152 signals[BEFORE_PAINT] =
153 g_signal_new (signal_name: g_intern_static_string (string: "before-paint"),
154 GDK_TYPE_FRAME_CLOCK,
155 signal_flags: G_SIGNAL_RUN_LAST,
156 class_offset: 0,
157 NULL, NULL, NULL,
158 G_TYPE_NONE, n_params: 0);
159
160 /**
161 * GdkFrameClock::update:
162 * @clock: the frame clock emitting the signal
163 *
164 * Emitted as the first step of toolkit and application processing
165 * of the frame.
166 *
167 * Animations should be updated using [method@Gdk.FrameClock.get_frame_time].
168 * Applications can connect directly to this signal, or use
169 * [method@Gtk.Widget.add_tick_callback] as a more convenient interface.
170 */
171 signals[UPDATE] =
172 g_signal_new (signal_name: g_intern_static_string (string: "update"),
173 GDK_TYPE_FRAME_CLOCK,
174 signal_flags: G_SIGNAL_RUN_LAST,
175 class_offset: 0,
176 NULL, NULL, NULL,
177 G_TYPE_NONE, n_params: 0);
178
179 /**
180 * GdkFrameClock::layout:
181 * @clock: the frame clock emitting the signal
182 *
183 * Emitted as the second step of toolkit and application processing
184 * of the frame.
185 *
186 * Any work to update sizes and positions of application elements
187 * should be performed. GTK normally handles this internally.
188 */
189 signals[LAYOUT] =
190 g_signal_new (signal_name: g_intern_static_string (string: "layout"),
191 GDK_TYPE_FRAME_CLOCK,
192 signal_flags: G_SIGNAL_RUN_LAST,
193 class_offset: 0,
194 NULL, NULL, NULL,
195 G_TYPE_NONE, n_params: 0);
196
197 /**
198 * GdkFrameClock::paint:
199 * @clock: the frame clock emitting the signal
200 *
201 * Emitted as the third step of toolkit and application processing
202 * of the frame.
203 *
204 * The frame is repainted. GDK normally handles this internally and
205 * emits [signal@Gdk.Surface::render] signals which are turned into
206 * [signal@Gtk.Widget::snapshot] signals by GTK.
207 */
208 signals[PAINT] =
209 g_signal_new (signal_name: g_intern_static_string (string: "paint"),
210 GDK_TYPE_FRAME_CLOCK,
211 signal_flags: G_SIGNAL_RUN_LAST,
212 class_offset: 0,
213 NULL, NULL, NULL,
214 G_TYPE_NONE, n_params: 0);
215
216 /**
217 * GdkFrameClock::after-paint:
218 * @clock: the frame clock emitting the signal
219 *
220 * This signal ends processing of the frame.
221 *
222 * Applications should generally not handle this signal.
223 */
224 signals[AFTER_PAINT] =
225 g_signal_new (signal_name: g_intern_static_string (string: "after-paint"),
226 GDK_TYPE_FRAME_CLOCK,
227 signal_flags: G_SIGNAL_RUN_LAST,
228 class_offset: 0,
229 NULL, NULL, NULL,
230 G_TYPE_NONE, n_params: 0);
231
232 /**
233 * GdkFrameClock::resume-events:
234 * @clock: the frame clock emitting the signal
235 *
236 * Emitted after processing of the frame is finished.
237 *
238 * This signal is handled internally by GTK to resume normal
239 * event processing. Applications should not handle this signal.
240 */
241 signals[RESUME_EVENTS] =
242 g_signal_new (signal_name: g_intern_static_string (string: "resume-events"),
243 GDK_TYPE_FRAME_CLOCK,
244 signal_flags: G_SIGNAL_RUN_LAST,
245 class_offset: 0,
246 NULL, NULL, NULL,
247 G_TYPE_NONE, n_params: 0);
248}
249
250static void
251gdk_frame_clock_init (GdkFrameClock *clock)
252{
253 GdkFrameClockPrivate *priv;
254
255 clock->priv = priv = gdk_frame_clock_get_instance_private (self: clock);
256
257 priv->frame_counter = -1;
258 priv->current = FRAME_HISTORY_MAX_LENGTH - 1;
259
260 if (fps_counter == 0)
261 fps_counter = gdk_profiler_define_counter ("fps", "Frames per Second");
262}
263
264/**
265 * gdk_frame_clock_get_frame_time:
266 * @frame_clock: a `GdkFrameClock`
267 *
268 * Gets the time that should currently be used for animations.
269 *
270 * Inside the processing of a frame, it’s the time used to compute the
271 * animation position of everything in a frame. Outside of a frame, it's
272 * the time of the conceptual “previous frame,” which may be either
273 * the actual previous frame time, or if that’s too old, an updated
274 * time.
275 *
276 * Returns: a timestamp in microseconds, in the timescale of
277 * of g_get_monotonic_time().
278 */
279gint64
280gdk_frame_clock_get_frame_time (GdkFrameClock *frame_clock)
281{
282 g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
283
284 return GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->get_frame_time (frame_clock);
285}
286
287/**
288 * gdk_frame_clock_request_phase:
289 * @frame_clock: a `GdkFrameClock`
290 * @phase: the phase that is requested
291 *
292 * Asks the frame clock to run a particular phase.
293 *
294 * The signal corresponding the requested phase will be emitted the next
295 * time the frame clock processes. Multiple calls to
296 * gdk_frame_clock_request_phase() will be combined together
297 * and only one frame processed. If you are displaying animated
298 * content and want to continually request the
299 * %GDK_FRAME_CLOCK_PHASE_UPDATE phase for a period of time,
300 * you should use [method@Gdk.FrameClock.begin_updating] instead,
301 * since this allows GTK to adjust system parameters to get maximally
302 * smooth animations.
303 */
304void
305gdk_frame_clock_request_phase (GdkFrameClock *frame_clock,
306 GdkFrameClockPhase phase)
307{
308 g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
309
310 GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->request_phase (frame_clock, phase);
311}
312
313/**
314 * gdk_frame_clock_begin_updating:
315 * @frame_clock: a `GdkFrameClock`
316 *
317 * Starts updates for an animation.
318 *
319 * Until a matching call to [method@Gdk.FrameClock.end_updating] is made,
320 * the frame clock will continually request a new frame with the
321 * %GDK_FRAME_CLOCK_PHASE_UPDATE phase. This function may be called multiple
322 * times and frames will be requested until gdk_frame_clock_end_updating()
323 * is called the same number of times.
324 */
325void
326gdk_frame_clock_begin_updating (GdkFrameClock *frame_clock)
327{
328 g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
329
330 GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->begin_updating (frame_clock);
331}
332
333/**
334 * gdk_frame_clock_end_updating:
335 * @frame_clock: a `GdkFrameClock`
336 *
337 * Stops updates for an animation.
338 *
339 * See the documentation for [method@Gdk.FrameClock.begin_updating].
340 */
341void
342gdk_frame_clock_end_updating (GdkFrameClock *frame_clock)
343{
344 g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
345
346 GDK_FRAME_CLOCK_GET_CLASS (frame_clock)->end_updating (frame_clock);
347}
348
349static void
350_gdk_frame_clock_freeze (GdkFrameClock *clock)
351{
352 g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
353
354 GDK_FRAME_CLOCK_GET_CLASS (clock)->freeze (clock);
355}
356
357static void
358_gdk_frame_clock_thaw (GdkFrameClock *clock)
359{
360 g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
361
362 GDK_FRAME_CLOCK_GET_CLASS (clock)->thaw (clock);
363}
364
365void
366_gdk_frame_clock_inhibit_freeze (GdkFrameClock *clock)
367{
368 GdkFrameClockPrivate *priv;
369
370 g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
371
372 priv = clock->priv;
373
374 priv->n_freeze_inhibitors++;
375 if (priv->n_freeze_inhibitors == 1)
376 _gdk_frame_clock_thaw (clock);
377}
378
379void
380_gdk_frame_clock_uninhibit_freeze (GdkFrameClock *clock)
381{
382 GdkFrameClockPrivate *priv;
383
384 g_return_if_fail (GDK_IS_FRAME_CLOCK (clock));
385
386 priv = clock->priv;
387
388 priv->n_freeze_inhibitors--;
389 if (priv->n_freeze_inhibitors == 0)
390 _gdk_frame_clock_freeze (clock);
391}
392
393/**
394 * gdk_frame_clock_get_frame_counter:
395 * @frame_clock: a `GdkFrameClock`
396 *
397 * `GdkFrameClock` maintains a 64-bit counter that increments for
398 * each frame drawn.
399 *
400 * Returns: inside frame processing, the value of the frame counter
401 * for the current frame. Outside of frame processing, the frame
402 * counter for the last frame.
403 */
404gint64
405gdk_frame_clock_get_frame_counter (GdkFrameClock *frame_clock)
406{
407 GdkFrameClockPrivate *priv;
408
409 g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
410
411 priv = frame_clock->priv;
412
413 return priv->frame_counter;
414}
415
416/**
417 * gdk_frame_clock_get_history_start:
418 * @frame_clock: a `GdkFrameClock`
419 *
420 * Returns the frame counter for the oldest frame available in history.
421 *
422 * `GdkFrameClock` internally keeps a history of `GdkFrameTimings`
423 * objects for recent frames that can be retrieved with
424 * [method@Gdk.FrameClock.get_timings]. The set of stored frames
425 * is the set from the counter values given by
426 * [method@Gdk.FrameClock.get_history_start] and
427 * [method@Gdk.FrameClock.get_frame_counter], inclusive.
428 *
429 * Returns: the frame counter value for the oldest frame
430 * that is available in the internal frame history of the
431 * `GdkFrameClock`
432 */
433gint64
434gdk_frame_clock_get_history_start (GdkFrameClock *frame_clock)
435{
436 GdkFrameClockPrivate *priv;
437
438 g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
439
440 priv = frame_clock->priv;
441
442 return priv->frame_counter + 1 - priv->n_timings;
443}
444
445void
446_gdk_frame_clock_begin_frame (GdkFrameClock *frame_clock)
447{
448 GdkFrameClockPrivate *priv;
449
450 g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
451
452 priv = frame_clock->priv;
453
454 priv->frame_counter++;
455 priv->current = (priv->current + 1) % FRAME_HISTORY_MAX_LENGTH;
456
457 /* Try to steal the previous frame timing instead of discarding
458 * and allocating a new one.
459 */
460 if G_LIKELY (priv->n_timings == FRAME_HISTORY_MAX_LENGTH &&
461 _gdk_frame_timings_steal (priv->timings[priv->current],
462 priv->frame_counter))
463 return;
464
465 if (priv->n_timings < FRAME_HISTORY_MAX_LENGTH)
466 priv->n_timings++;
467 else
468 gdk_frame_timings_unref (timings: priv->timings[priv->current]);
469
470 priv->timings[priv->current] = _gdk_frame_timings_new (frame_counter: priv->frame_counter);
471}
472
473/**
474 * gdk_frame_clock_get_timings:
475 * @frame_clock: a `GdkFrameClock`
476 * @frame_counter: the frame counter value identifying the frame to
477 * be received
478 *
479 * Retrieves a `GdkFrameTimings` object holding timing information
480 * for the current frame or a recent frame.
481 *
482 * The `GdkFrameTimings` object may not yet be complete: see
483 * [method@Gdk.FrameTimings.get_complete] and
484 * [method@Gdk.FrameClock.get_history_start].
485 *
486 * Returns: (nullable) (transfer none): the `GdkFrameTimings` object
487 * for the specified frame, or %NULL if it is not available
488 */
489GdkFrameTimings *
490gdk_frame_clock_get_timings (GdkFrameClock *frame_clock,
491 gint64 frame_counter)
492{
493 GdkFrameClockPrivate *priv;
494 int pos;
495
496 g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), NULL);
497
498 priv = frame_clock->priv;
499
500 if (frame_counter > priv->frame_counter)
501 return NULL;
502
503 if (frame_counter <= priv->frame_counter - priv->n_timings)
504 return NULL;
505
506 pos = (priv->current - (priv->frame_counter - frame_counter) + FRAME_HISTORY_MAX_LENGTH) % FRAME_HISTORY_MAX_LENGTH;
507
508 return priv->timings[pos];
509}
510
511/**
512 * gdk_frame_clock_get_current_timings:
513 * @frame_clock: a `GdkFrameClock`
514 *
515 * Gets the frame timings for the current frame.
516 *
517 * Returns: (nullable) (transfer none): the `GdkFrameTimings` for the
518 * frame currently being processed, or even no frame is being
519 * processed, for the previous frame. Before any frames have been
520 * processed, returns %NULL.
521 */
522GdkFrameTimings *
523gdk_frame_clock_get_current_timings (GdkFrameClock *frame_clock)
524{
525 GdkFrameClockPrivate *priv;
526
527 g_return_val_if_fail (GDK_IS_FRAME_CLOCK (frame_clock), 0);
528
529 priv = frame_clock->priv;
530
531 return gdk_frame_clock_get_timings (frame_clock, frame_counter: priv->frame_counter);
532}
533
534
535#ifdef G_ENABLE_DEBUG
536void
537_gdk_frame_clock_debug_print_timings (GdkFrameClock *clock,
538 GdkFrameTimings *timings)
539{
540 GString *str;
541
542 gint64 previous_frame_time = 0;
543 gint64 previous_smoothed_frame_time = 0;
544 GdkFrameTimings *previous_timings = gdk_frame_clock_get_timings (frame_clock: clock,
545 frame_counter: timings->frame_counter - 1);
546
547 if (previous_timings != NULL)
548 {
549 previous_frame_time = previous_timings->frame_time;
550 previous_smoothed_frame_time = previous_timings->smoothed_frame_time;
551 }
552
553 str = g_string_new (init: "");
554
555 g_string_append_printf (string: str, format: "%5" G_GINT64_FORMAT ":", timings->frame_counter);
556 if (previous_frame_time != 0)
557 {
558 g_string_append_printf (string: str, format: " interval=%-4.1f", (timings->frame_time - previous_frame_time) / 1000.);
559 g_string_append_printf (string: str, format: timings->slept_before ? " (sleep)" : " ");
560 g_string_append_printf (string: str, format: " smoothed=%4.1f / %-4.1f",
561 (timings->smoothed_frame_time - timings->frame_time) / 1000.,
562 (timings->smoothed_frame_time - previous_smoothed_frame_time) / 1000.);
563 }
564 if (timings->layout_start_time != 0)
565 g_string_append_printf (string: str, format: " layout_start=%-4.1f", (timings->layout_start_time - timings->frame_time) / 1000.);
566 if (timings->paint_start_time != 0)
567 g_string_append_printf (string: str, format: " paint_start=%-4.1f", (timings->paint_start_time - timings->frame_time) / 1000.);
568 if (timings->frame_end_time != 0)
569 g_string_append_printf (string: str, format: " frame_end=%-4.1f", (timings->frame_end_time - timings->frame_time) / 1000.);
570 if (timings->drawn_time != 0)
571 g_string_append_printf (string: str, format: " drawn=%-4.1f", (timings->drawn_time - timings->frame_time) / 1000.);
572 if (timings->presentation_time != 0)
573 g_string_append_printf (string: str, format: " present=%-4.1f", (timings->presentation_time - timings->frame_time) / 1000.);
574 if (timings->predicted_presentation_time != 0)
575 g_string_append_printf (string: str, format: " predicted=%-4.1f", (timings->predicted_presentation_time - timings->frame_time) / 1000.);
576 if (timings->refresh_interval != 0)
577 g_string_append_printf (string: str, format: " refresh_interval=%-4.1f", timings->refresh_interval / 1000.);
578
579 g_message ("%s", str->str);
580 g_string_free (string: str, TRUE);
581}
582#endif /* G_ENABLE_DEBUG */
583
584#define DEFAULT_REFRESH_INTERVAL 16667 /* 16.7ms (1/60th second) */
585#define MAX_HISTORY_AGE 150000 /* 150ms */
586
587/**
588 * gdk_frame_clock_get_refresh_info:
589 * @frame_clock: a `GdkFrameClock`
590 * @base_time: base time for determining a presentaton time
591 * @refresh_interval_return: (out) (optional): a location to store the
592 * determined refresh interval, or %NULL. A default refresh interval of
593 * 1/60th of a second will be stored if no history is present.
594 * @presentation_time_return: (out): a location to store the next
595 * candidate presentation time after the given base time.
596 * 0 will be will be stored if no history is present.
597 *
598 * Predicts a presentation time, based on history.
599 *
600 * Using the frame history stored in the frame clock, finds the last
601 * known presentation time and refresh interval, and assuming that
602 * presentation times are separated by the refresh interval,
603 * predicts a presentation time that is a multiple of the refresh
604 * interval after the last presentation time, and later than @base_time.
605 */
606void
607gdk_frame_clock_get_refresh_info (GdkFrameClock *frame_clock,
608 gint64 base_time,
609 gint64 *refresh_interval_return,
610 gint64 *presentation_time_return)
611{
612 gint64 frame_counter;
613 gint64 default_refresh_interval = DEFAULT_REFRESH_INTERVAL;
614
615 g_return_if_fail (GDK_IS_FRAME_CLOCK (frame_clock));
616
617 frame_counter = gdk_frame_clock_get_frame_counter (frame_clock);
618
619 while (TRUE)
620 {
621 GdkFrameTimings *timings = gdk_frame_clock_get_timings (frame_clock, frame_counter);
622 gint64 presentation_time;
623 gint64 refresh_interval;
624
625 if (timings == NULL)
626 break;
627
628 refresh_interval = timings->refresh_interval;
629 presentation_time = timings->presentation_time;
630
631 if (refresh_interval == 0)
632 refresh_interval = default_refresh_interval;
633 else
634 default_refresh_interval = refresh_interval;
635
636 if (presentation_time != 0)
637 {
638 if (presentation_time > base_time - MAX_HISTORY_AGE &&
639 presentation_time_return)
640 {
641 if (refresh_interval_return)
642 *refresh_interval_return = refresh_interval;
643
644 while (presentation_time < base_time)
645 presentation_time += refresh_interval;
646
647 if (presentation_time_return)
648 *presentation_time_return = presentation_time;
649
650 return;
651 }
652
653 break;
654 }
655
656 frame_counter--;
657 }
658
659 if (presentation_time_return)
660 *presentation_time_return = 0;
661 if (refresh_interval_return)
662 *refresh_interval_return = default_refresh_interval;
663}
664
665void
666_gdk_frame_clock_emit_flush_events (GdkFrameClock *frame_clock)
667{
668 g_signal_emit (instance: frame_clock, signal_id: signals[FLUSH_EVENTS], detail: 0);
669}
670
671void
672_gdk_frame_clock_emit_before_paint (GdkFrameClock *frame_clock)
673{
674 g_signal_emit (instance: frame_clock, signal_id: signals[BEFORE_PAINT], detail: 0);
675}
676
677void
678_gdk_frame_clock_emit_update (GdkFrameClock *frame_clock)
679{
680 gint64 before G_GNUC_UNUSED;
681
682 before = GDK_PROFILER_CURRENT_TIME;
683
684 g_signal_emit (instance: frame_clock, signal_id: signals[UPDATE], detail: 0);
685
686 gdk_profiler_end_mark (before, "frameclock update", NULL);
687}
688
689void
690_gdk_frame_clock_emit_layout (GdkFrameClock *frame_clock)
691{
692 gint64 before G_GNUC_UNUSED;
693
694 before = GDK_PROFILER_CURRENT_TIME;
695
696 g_signal_emit (instance: frame_clock, signal_id: signals[LAYOUT], detail: 0);
697
698 gdk_profiler_end_mark (before, "frameclock layout", NULL);
699}
700
701void
702_gdk_frame_clock_emit_paint (GdkFrameClock *frame_clock)
703{
704 gint64 before G_GNUC_UNUSED;
705
706 before = GDK_PROFILER_CURRENT_TIME;
707
708 g_signal_emit (instance: frame_clock, signal_id: signals[PAINT], detail: 0);
709
710 gdk_profiler_end_mark (before, "frameclock paint", NULL);
711}
712
713void
714_gdk_frame_clock_emit_after_paint (GdkFrameClock *frame_clock)
715{
716 g_signal_emit (instance: frame_clock, signal_id: signals[AFTER_PAINT], detail: 0);
717}
718
719void
720_gdk_frame_clock_emit_resume_events (GdkFrameClock *frame_clock)
721{
722 g_signal_emit (instance: frame_clock, signal_id: signals[RESUME_EVENTS], detail: 0);
723}
724
725static gint64
726guess_refresh_interval (GdkFrameClock *frame_clock)
727{
728 gint64 interval;
729 gint64 i;
730
731 interval = G_MAXINT64;
732
733 for (i = gdk_frame_clock_get_history_start (frame_clock);
734 i < gdk_frame_clock_get_frame_counter (frame_clock);
735 i++)
736 {
737 GdkFrameTimings *t, *before;
738 gint64 ts, before_ts;
739
740 t = gdk_frame_clock_get_timings (frame_clock, frame_counter: i);
741 before = gdk_frame_clock_get_timings (frame_clock, frame_counter: i - 1);
742 if (t == NULL || before == NULL)
743 continue;
744
745 ts = gdk_frame_timings_get_frame_time (timings: t);
746 before_ts = gdk_frame_timings_get_frame_time (timings: before);
747 if (ts == 0 || before_ts == 0)
748 continue;
749
750 interval = MIN (interval, ts - before_ts);
751 }
752
753 if (interval == G_MAXINT64)
754 return 0;
755
756 return interval;
757}
758
759/**
760 * gdk_frame_clock_get_fps:
761 * @frame_clock: a `GdkFrameClock`
762 *
763 * Calculates the current frames-per-second, based on the
764 * frame timings of @frame_clock.
765 *
766 * Returns: the current fps, as a `double`
767 */
768double
769gdk_frame_clock_get_fps (GdkFrameClock *frame_clock)
770{
771 GdkFrameTimings *start, *end;
772 gint64 start_counter, end_counter;
773 gint64 start_timestamp, end_timestamp;
774 gint64 interval;
775
776 start_counter = gdk_frame_clock_get_history_start (frame_clock);
777 end_counter = gdk_frame_clock_get_frame_counter (frame_clock);
778 start = gdk_frame_clock_get_timings (frame_clock, frame_counter: start_counter);
779 for (end = gdk_frame_clock_get_timings (frame_clock, frame_counter: end_counter);
780 end_counter > start_counter && end != NULL && !gdk_frame_timings_get_complete (timings: end);
781 end = gdk_frame_clock_get_timings (frame_clock, frame_counter: end_counter))
782 end_counter--;
783 if (end_counter - start_counter < 4)
784 return 0.0;
785
786 start_timestamp = gdk_frame_timings_get_presentation_time (timings: start);
787 end_timestamp = gdk_frame_timings_get_presentation_time (timings: end);
788 if (start_timestamp == 0 || end_timestamp == 0)
789 {
790 start_timestamp = gdk_frame_timings_get_frame_time (timings: start);
791 end_timestamp = gdk_frame_timings_get_frame_time (timings: end);
792 }
793 interval = gdk_frame_timings_get_refresh_interval (timings: end);
794 if (interval == 0)
795 {
796 interval = guess_refresh_interval (frame_clock);
797 if (interval == 0)
798 return 0.0;
799 }
800
801 return ((double) end_counter - start_counter) * G_USEC_PER_SEC / (end_timestamp - start_timestamp);
802}
803
804void
805_gdk_frame_clock_add_timings_to_profiler (GdkFrameClock *clock,
806 GdkFrameTimings *timings)
807{
808 if (timings->drawn_time != 0)
809 {
810 gdk_profiler_add_mark (1000 * timings->drawn_time, 0, "drawn window", NULL);
811 }
812
813 if (timings->presentation_time != 0)
814 {
815 gdk_profiler_add_mark (1000 * timings->presentation_time, 0, "presented window", NULL);
816 }
817
818 gdk_profiler_set_counter (fps_counter, gdk_frame_clock_get_fps (clock));
819}
820

source code of gtk/gdk/gdkframeclock.c