1/*
2 * Copyright © 2018 Benjamin Otte
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.1 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 * Authors: Benjamin Otte <otte@gnome.org>
18 */
19
20#include "config.h"
21
22#include "fpsoverlay.h"
23
24#include "gtkintl.h"
25#include "gtkwidget.h"
26#include "gtkwindow.h"
27#include "gtknative.h"
28
29/* duration before we start fading in us */
30#define GDK_FPS_OVERLAY_LINGER_DURATION (1000 * 1000)
31/* duration when fade is finished in us */
32#define GDK_FPS_OVERLAY_FADE_DURATION (500 * 1000)
33
34typedef struct _GtkFpsInfo {
35 gint64 last_frame;
36 GskRenderNode *last_node;
37} GtkFpsInfo;
38
39struct _GtkFpsOverlay
40{
41 GtkInspectorOverlay parent_instance;
42
43 GHashTable *infos; /* GtkWidget => GtkFpsInfo */
44};
45
46struct _GtkFpsOverlayClass
47{
48 GtkInspectorOverlayClass parent_class;
49};
50
51G_DEFINE_TYPE (GtkFpsOverlay, gtk_fps_overlay, GTK_TYPE_INSPECTOR_OVERLAY)
52
53static void
54gtk_fps_info_free (gpointer data)
55{
56 GtkFpsInfo *info = data;
57
58 gsk_render_node_unref (node: info->last_node);
59
60 g_slice_free (GtkFpsInfo, info);
61}
62
63static double
64gtk_fps_overlay_get_fps (GtkWidget *widget)
65{
66 GdkFrameClock *frame_clock;
67
68 frame_clock = gtk_widget_get_frame_clock (widget);
69 if (frame_clock == NULL)
70 return 0.0;
71
72 return gdk_frame_clock_get_fps (frame_clock);
73}
74
75static gboolean
76gtk_fps_overlay_force_redraw (GtkWidget *widget,
77 GdkFrameClock *clock,
78 gpointer unused)
79{
80 gdk_surface_queue_render (surface: gtk_native_get_surface (self: gtk_widget_get_native (widget)));
81
82 return G_SOURCE_REMOVE;
83}
84
85static void
86gtk_fps_overlay_snapshot (GtkInspectorOverlay *overlay,
87 GtkSnapshot *snapshot,
88 GskRenderNode *node,
89 GtkWidget *widget)
90{
91 GtkFpsOverlay *self = GTK_FPS_OVERLAY (ptr: overlay);
92 GtkFpsInfo *info;
93 PangoLayout *layout;
94 PangoAttrList *attrs;
95 gint64 now;
96 double fps;
97 char *fps_string;
98 graphene_rect_t bounds;
99 gboolean has_bounds;
100 int width, height;
101 double overlay_opacity;
102
103 now = gdk_frame_clock_get_frame_time (frame_clock: gtk_widget_get_frame_clock (widget));
104 info = g_hash_table_lookup (hash_table: self->infos, key: widget);
105 if (info == NULL)
106 {
107 info = g_slice_new0 (GtkFpsInfo);
108 g_hash_table_insert (hash_table: self->infos, key: widget, value: info);
109 }
110 if (info->last_node != node)
111 {
112 g_clear_pointer (&info->last_node, gsk_render_node_unref);
113 info->last_node = gsk_render_node_ref (node);
114 info->last_frame = now;
115 overlay_opacity = 1.0;
116 }
117 else
118 {
119 if (now - info->last_frame > GDK_FPS_OVERLAY_LINGER_DURATION + GDK_FPS_OVERLAY_FADE_DURATION)
120 {
121 g_hash_table_remove (hash_table: self->infos, key: widget);
122 return;
123 }
124 else if (now - info->last_frame > GDK_FPS_OVERLAY_LINGER_DURATION)
125 {
126 overlay_opacity = 1.0 - (double) (now - info->last_frame - GDK_FPS_OVERLAY_LINGER_DURATION)
127 / GDK_FPS_OVERLAY_FADE_DURATION;
128 }
129 else
130 {
131 overlay_opacity = 1.0;
132 }
133 }
134
135 fps = gtk_fps_overlay_get_fps (widget);
136 if (fps == 0.0)
137 fps_string = g_strdup (str: "--- fps");
138 else
139 fps_string = g_strdup_printf (format: "%.2f fps", fps);
140
141 if (GTK_IS_WINDOW (widget))
142 {
143 GtkWidget *child = gtk_window_get_child (GTK_WINDOW (widget));
144 if (!child ||
145 !gtk_widget_compute_bounds (widget: child, target: widget, out_bounds: &bounds))
146 has_bounds = gtk_widget_compute_bounds (widget, target: widget, out_bounds: &bounds);
147 else
148 has_bounds = gtk_widget_compute_bounds (widget: child, target: widget, out_bounds: &bounds);
149 }
150 else
151 {
152 has_bounds = gtk_widget_compute_bounds (widget, target: widget, out_bounds: &bounds);
153 }
154
155 layout = gtk_widget_create_pango_layout (widget, text: fps_string);
156 attrs = pango_attr_list_new ();
157 pango_attr_list_insert (list: attrs, attr: pango_attr_font_features_new (features: "tnum=1"));
158 pango_layout_set_attributes (layout, attrs);
159 pango_attr_list_unref (list: attrs);
160 pango_layout_get_pixel_size (layout, width: &width, height: &height);
161
162 gtk_snapshot_save (snapshot);
163 if (has_bounds)
164 gtk_snapshot_translate (snapshot, point: &GRAPHENE_POINT_INIT (bounds.origin.x + bounds.size.width - width, bounds.origin.y));
165 if (overlay_opacity < 1.0)
166 gtk_snapshot_push_opacity (snapshot, opacity: overlay_opacity);
167 gtk_snapshot_append_color (snapshot,
168 color: &(GdkRGBA) { 0, 0, 0, 0.5 },
169 bounds: &GRAPHENE_RECT_INIT (-1, -1, width + 2, height + 2));
170 gtk_snapshot_append_layout (snapshot,
171 layout,
172 color: &(GdkRGBA) { 1, 1, 1, 1 });
173 if (overlay_opacity < 1.0)
174 gtk_snapshot_pop (snapshot);
175 gtk_snapshot_restore (snapshot);
176 g_free (mem: fps_string);
177
178 gtk_widget_add_tick_callback (widget, callback: gtk_fps_overlay_force_redraw, NULL, NULL);
179}
180
181static void
182gtk_fps_overlay_queue_draw (GtkInspectorOverlay *overlay)
183{
184 GtkFpsOverlay *self = GTK_FPS_OVERLAY (ptr: overlay);
185 GHashTableIter iter;
186 gpointer widget;
187
188 g_hash_table_iter_init (iter: &iter, hash_table: self->infos);
189 while (g_hash_table_iter_next (iter: &iter, key: &widget, NULL))
190 gdk_surface_queue_render (surface: gtk_native_get_surface (self: gtk_widget_get_native (widget)));
191}
192
193static void
194gtk_fps_overlay_dispose (GObject *object)
195{
196 GtkFpsOverlay *self = GTK_FPS_OVERLAY (ptr: object);
197
198 g_hash_table_unref (hash_table: self->infos);
199
200 G_OBJECT_CLASS (gtk_fps_overlay_parent_class)->dispose (object);
201}
202
203static void
204gtk_fps_overlay_class_init (GtkFpsOverlayClass *klass)
205{
206 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
207 GtkInspectorOverlayClass *overlay_class = GTK_INSPECTOR_OVERLAY_CLASS (ptr: klass);
208
209 overlay_class->snapshot = gtk_fps_overlay_snapshot;
210 overlay_class->queue_draw = gtk_fps_overlay_queue_draw;
211
212 gobject_class->dispose = gtk_fps_overlay_dispose;
213}
214
215static void
216gtk_fps_overlay_init (GtkFpsOverlay *self)
217{
218 self->infos = g_hash_table_new_full (hash_func: g_direct_hash, key_equal_func: g_direct_equal, NULL, value_destroy_func: gtk_fps_info_free);
219}
220
221GtkInspectorOverlay *
222gtk_fps_overlay_new (void)
223{
224 GtkFpsOverlay *self;
225
226 self = g_object_new (GTK_TYPE_FPS_OVERLAY, NULL);
227
228 return GTK_INSPECTOR_OVERLAY (ptr: self);
229}
230
231

source code of gtk/gtk/inspector/fpsoverlay.c