Commit | Line | Data |
---|---|---|
3144ee8a AT |
1 | /* fps, Copyright (c) 2001-2019 Jamie Zawinski <jwz@jwz.org> |
2 | * Draw a frames-per-second display (Xlib and OpenGL). | |
3 | * | |
4 | * Permission to use, copy, modify, distribute, and sell this software and its | |
5 | * documentation for any purpose is hereby granted without fee, provided that | |
6 | * the above copyright notice appear in all copies and that both that | |
7 | * copyright notice and this permission notice appear in supporting | |
8 | * documentation. No representations are made about the suitability of this | |
9 | * software for any purpose. It is provided "as is" without express or | |
10 | * implied warranty. | |
11 | */ | |
12 | ||
13 | #ifdef HAVE_CONFIG_H | |
14 | # include "config.h" | |
15 | #endif /* HAVE_CONFIG_H */ | |
16 | ||
17 | #include <time.h> | |
18 | #include "screenhackI.h" | |
19 | #include "fpsI.h" | |
20 | ||
21 | fps_state * | |
22 | fps_init (Display *dpy, Window window) | |
23 | { | |
24 | fps_state *st; | |
25 | const char *font; | |
26 | XFontStruct *f; | |
27 | Bool top_p; | |
28 | XWindowAttributes xgwa; | |
29 | ||
30 | if (! get_boolean_resource (dpy, "doFPS", "DoFPS")) | |
31 | return 0; | |
32 | ||
33 | if (!strcasecmp (progname, "BSOD")) return 0; /* Never worked right */ | |
34 | ||
35 | top_p = get_boolean_resource (dpy, "fpsTop", "FPSTop"); | |
36 | ||
37 | st = (fps_state *) calloc (1, sizeof(*st)); | |
38 | ||
39 | st->dpy = dpy; | |
40 | st->window = window; | |
41 | st->clear_p = get_boolean_resource (dpy, "fpsSolid", "FPSSolid"); | |
42 | ||
43 | font = get_string_resource (dpy, "fpsFont", "Font"); | |
44 | ||
45 | if (!font) | |
46 | font = "-*-courier-bold-r-normal-*-*-180-*-*-*-*-*-*"; /* also texfont.c */ | |
47 | f = load_font_retry (dpy, font); | |
48 | if (!f) abort(); | |
49 | ||
50 | { | |
51 | XGCValues gcv; | |
52 | XGetWindowAttributes (dpy, window, &xgwa); | |
53 | gcv.font = f->fid; | |
54 | gcv.foreground = | |
55 | get_pixel_resource (st->dpy, xgwa.colormap, "foreground", "Foreground"); | |
56 | st->draw_gc = XCreateGC (dpy, window, GCFont|GCForeground, &gcv); | |
57 | gcv.foreground = | |
58 | get_pixel_resource (st->dpy, xgwa.colormap, "background", "Background"); | |
59 | st->erase_gc = XCreateGC (dpy, window, GCFont|GCForeground, &gcv); | |
60 | } | |
61 | ||
62 | st->font = f; | |
63 | st->x = 10; | |
64 | st->y = 10; | |
65 | if (top_p) | |
66 | st->y = - (st->font->ascent + st->font->descent + 10); | |
67 | ||
68 | # ifdef HAVE_IPHONE | |
69 | /* Don't hide the FPS display under the iPhone X bezel. | |
70 | #### This is the worst of all possible ways to do this! But how else? | |
71 | */ | |
72 | if (xgwa.width == 2436 || xgwa.height == 2436) | |
73 | { | |
74 | st->x += 48; | |
75 | st->y += 48 * (top_p ? -1 : 1); | |
76 | } | |
77 | # endif | |
78 | ||
79 | strcpy (st->string, "FPS: ... "); | |
80 | ||
81 | return st; | |
82 | } | |
83 | ||
84 | void | |
85 | fps_free (fps_state *st) | |
86 | { | |
87 | if (st->draw_gc) XFreeGC (st->dpy, st->draw_gc); | |
88 | if (st->erase_gc) XFreeGC (st->dpy, st->erase_gc); | |
89 | if (st->font) XFreeFont (st->dpy, st->font); | |
90 | free (st); | |
91 | } | |
92 | ||
93 | ||
94 | void | |
95 | fps_slept (fps_state *st, unsigned long usecs) | |
96 | { | |
97 | st->slept += usecs; | |
98 | } | |
99 | ||
100 | ||
101 | double | |
102 | fps_compute (fps_state *st, unsigned long polys, double depth) | |
103 | { | |
104 | if (! st) return 0; /* too early? */ | |
105 | ||
106 | /* Every N frames (where N is approximately one second's worth of frames) | |
107 | check the wall clock. We do this because checking the wall clock is | |
108 | a slow operation. | |
109 | */ | |
110 | if (st->frame_count++ >= st->last_ifps) | |
111 | { | |
112 | # ifdef GETTIMEOFDAY_TWO_ARGS | |
113 | struct timezone tzp; | |
114 | gettimeofday(&st->this_frame_end, &tzp); | |
115 | # else | |
116 | gettimeofday(&st->this_frame_end); | |
117 | # endif | |
118 | ||
119 | if (st->prev_frame_end.tv_sec == 0) | |
120 | st->prev_frame_end = st->this_frame_end; | |
121 | } | |
122 | ||
123 | /* If we've probed the wall-clock time, regenerate the string. | |
124 | */ | |
125 | if (st->this_frame_end.tv_sec != st->prev_frame_end.tv_sec) | |
126 | { | |
127 | double uprev_frame_end = (st->prev_frame_end.tv_sec + | |
128 | ((double) st->prev_frame_end.tv_usec | |
129 | * 0.000001)); | |
130 | double uthis_frame_end = (st->this_frame_end.tv_sec + | |
131 | ((double) st->this_frame_end.tv_usec | |
132 | * 0.000001)); | |
133 | double fps = st->frame_count / (uthis_frame_end - uprev_frame_end); | |
134 | double idle = (((double) st->slept * 0.000001) / | |
135 | (uthis_frame_end - uprev_frame_end)); | |
136 | double load = 100 * (1 - idle); | |
137 | ||
138 | if (load < 0) load = 0; /* well that's obviously nonsense... */ | |
139 | ||
140 | st->prev_frame_end = st->this_frame_end; | |
141 | st->frame_count = 0; | |
142 | st->slept = 0; | |
143 | st->last_ifps = fps; | |
144 | st->last_fps = fps; | |
145 | ||
146 | sprintf (st->string, (polys | |
147 | ? "FPS: %.1f \nLoad: %.1f%% " | |
148 | : "FPS: %.1f \nLoad: %.1f%% "), | |
149 | fps, load); | |
150 | ||
151 | if (polys > 0) | |
152 | { | |
153 | const char *s = ""; | |
154 | # if 0 | |
155 | if (polys >= (1024 * 1024)) polys >>= 20, s = "M"; | |
156 | else if (polys >= 2048) polys >>= 10, s = "K"; | |
157 | # endif | |
158 | ||
159 | strcat (st->string, "\nPolys: "); | |
160 | if (polys >= 1000000) | |
161 | sprintf (st->string + strlen(st->string), "%lu,%03lu,%03lu%s ", | |
162 | (polys / 1000000), ((polys / 1000) % 1000), | |
163 | (polys % 1000), s); | |
164 | else if (polys >= 1000) | |
165 | sprintf (st->string + strlen(st->string), "%lu,%03lu%s ", | |
166 | (polys / 1000), (polys % 1000), s); | |
167 | else | |
168 | sprintf (st->string + strlen(st->string), "%lu%s ", polys, s); | |
169 | } | |
170 | ||
171 | if (depth >= 0.0) | |
172 | { | |
173 | const char *s = ""; | |
174 | unsigned long ldepth = depth; | |
175 | # if 0 | |
176 | if (depth >= (1024 * 1024 * 1024)) | |
177 | ldepth = depth / (1024 * 1024 * 1024), s = "G"; | |
178 | else if (depth >= (1024 * 1024)) ldepth >>= 20, s = "M"; | |
179 | else if (depth >= 2048) ldepth >>= 10, s = "K"; | |
180 | # endif | |
181 | strcat (st->string, "\nDepth: "); | |
182 | if (ldepth >= 1000000000) | |
183 | sprintf (st->string + strlen(st->string), | |
184 | "%lu,%03lu,%03lu,%03lu%s ", | |
185 | (ldepth / 1000000000), | |
186 | ((ldepth / 1000000) % 1000), | |
187 | ((ldepth / 1000) % 1000), | |
188 | (ldepth % 1000), s); | |
189 | else if (ldepth >= 1000000) | |
190 | sprintf (st->string + strlen(st->string), "%lu,%03lu,%03lu%s ", | |
191 | (ldepth / 1000000), ((ldepth / 1000) % 1000), | |
192 | (ldepth % 1000), s); | |
193 | else if (ldepth >= 1000) | |
194 | sprintf (st->string + strlen(st->string), "%lu,%03lu%s ", | |
195 | (ldepth / 1000), (ldepth % 1000), s); | |
196 | else if (*s) | |
197 | sprintf (st->string + strlen(st->string), "%lu%s ", | |
198 | ldepth, s); | |
199 | else | |
200 | { | |
201 | int L; | |
202 | sprintf (st->string + strlen(st->string), "%.1f", depth); | |
203 | L = strlen (st->string); | |
204 | /* Remove trailing ".0" in case depth is not a fraction. */ | |
205 | if (st->string[L-2] == '.' && st->string[L-1] == '0') | |
206 | st->string[L-2] = 0; | |
207 | } | |
208 | } | |
209 | } | |
210 | ||
211 | return st->last_fps; | |
212 | } | |
213 | ||
214 | ||
215 | ||
216 | /* Width (and optionally height) of the string in pixels. | |
217 | */ | |
218 | static int | |
219 | string_width (XFontStruct *f, const char *c, int *height_ret) | |
220 | { | |
221 | int x = 0; | |
222 | int max_w = 0; | |
223 | int h = f->ascent + f->descent; | |
224 | while (*c) | |
225 | { | |
226 | int cc = *((unsigned char *) c); | |
227 | if (*c == '\n') | |
228 | { | |
229 | if (x > max_w) max_w = x; | |
230 | x = 0; | |
231 | h += f->ascent + f->descent; | |
232 | } | |
233 | else | |
234 | x += (f->per_char | |
235 | ? f->per_char[cc-f->min_char_or_byte2].width | |
236 | : f->min_bounds.rbearing); | |
237 | c++; | |
238 | } | |
239 | if (x > max_w) max_w = x; | |
240 | if (height_ret) *height_ret = h; | |
241 | ||
242 | return max_w; | |
243 | } | |
244 | ||
245 | ||
246 | /* This function is used only in Xlib mode. For GL mode, see glx/fps-gl.c. | |
247 | */ | |
248 | void | |
249 | fps_draw (fps_state *st) | |
250 | { | |
251 | XWindowAttributes xgwa; | |
252 | const char *string = st->string; | |
253 | const char *s; | |
254 | int x = st->x; | |
255 | int y = st->y; | |
256 | int lines = 1; | |
257 | int lh = st->font->ascent + st->font->descent; | |
258 | ||
259 | XGetWindowAttributes (st->dpy, st->window, &xgwa); | |
260 | ||
261 | for (s = string; *s; s++) | |
262 | if (*s == '\n') lines++; | |
263 | ||
264 | if (y < 0) | |
265 | y = -y + (lines-1) * lh; | |
266 | else | |
267 | y = xgwa.height - y; | |
268 | ||
269 | y -= lh * (lines-1) + st->font->descent; | |
270 | ||
271 | /* clear the background */ | |
272 | if (st->clear_p) | |
273 | { | |
274 | int w, h; | |
275 | w = string_width (st->font, string, &h); | |
276 | XFillRectangle (st->dpy, st->window, st->erase_gc, | |
277 | x - st->font->descent, | |
278 | y - lh, | |
279 | w + 2*st->font->descent, | |
280 | h + 2*st->font->descent); | |
281 | } | |
282 | ||
283 | /* draw the text */ | |
284 | while (lines) | |
285 | { | |
286 | s = strchr (string, '\n'); | |
287 | if (! s) s = string + strlen(string); | |
288 | XDrawString (st->dpy, st->window, st->draw_gc, | |
289 | x, y, string, (int) (s - string)); | |
290 | string = s; | |
291 | string++; | |
292 | lines--; | |
293 | y += lh; | |
294 | } | |
295 | } |