| 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 | } |