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 | |
67 | enum { |
68 | FLUSH_EVENTS, |
69 | BEFORE_PAINT, |
70 | UPDATE, |
71 | LAYOUT, |
72 | PAINT, |
73 | AFTER_PAINT, |
74 | RESUME_EVENTS, |
75 | LAST_SIGNAL |
76 | }; |
77 | |
78 | static guint signals[LAST_SIGNAL]; |
79 | |
80 | static guint fps_counter; |
81 | |
82 | #define FRAME_HISTORY_MAX_LENGTH 16 |
83 | |
84 | struct _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 | |
93 | G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GdkFrameClock, gdk_frame_clock, G_TYPE_OBJECT) |
94 | |
95 | static void |
96 | _gdk_frame_clock_freeze (GdkFrameClock *clock); |
97 | |
98 | static void |
99 | gdk_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 | |
111 | static void |
112 | gdk_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 | |
119 | static void |
120 | gdk_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 | |
250 | static void |
251 | gdk_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 | */ |
279 | gint64 |
280 | gdk_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 | */ |
304 | void |
305 | gdk_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 | */ |
325 | void |
326 | gdk_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 | */ |
341 | void |
342 | gdk_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 | |
349 | static 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 | |
357 | static 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 | |
365 | void |
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 | |
379 | void |
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 | */ |
404 | gint64 |
405 | gdk_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 | */ |
433 | gint64 |
434 | gdk_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 | |
445 | void |
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 | */ |
489 | GdkFrameTimings * |
490 | gdk_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 | */ |
522 | GdkFrameTimings * |
523 | gdk_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 |
536 | void |
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 | */ |
606 | void |
607 | gdk_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 | |
665 | void |
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 | |
671 | void |
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 | |
677 | void |
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 | |
689 | void |
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 | |
701 | void |
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 | |
713 | void |
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 | |
719 | void |
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 | |
725 | static gint64 |
726 | guess_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 | */ |
768 | double |
769 | gdk_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 | |
804 | void |
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 | |