7813a8a62f6121ac16927b25a73be38c10713768
[xscreensaver] / hacks / glx / fps.c
1 /* tube, Copyright (c) 2001, 2006 Jamie Zawinski <jwz@jwz.org>
2  * Utility function to draw a frames-per-second display.
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 "xlockmoreI.h"
18
19 #ifdef HAVE_COCOA
20 # include <OpenGL/gl.h>
21 # include <OpenGL/glu.h>
22 # include <AGL/agl.h>
23 #else /* !HAVE_COCOA -- real Xlib */
24 # include <GL/gl.h>
25 # include <GL/glu.h>
26 # include <GL/glx.h>
27 #endif /* !HAVE_COCOA */
28
29 #undef DEBUG  /* Defining this causes check_gl_error() to be called inside
30                  time-critical sections, which could slow things down (since
31                  it might result in a round-trip, and stall of the pipeline.)
32                */
33
34 extern void clear_gl_error (void);
35 extern void check_gl_error (const char *type);
36
37 extern GLfloat fps_1 (ModeInfo *mi);
38 extern void    fps_2 (ModeInfo *mi);
39 extern void    do_fps (ModeInfo *mi);
40 extern void    fps_free (ModeInfo *mi);
41
42 struct fps_state {
43   int x, y;
44   int ascent, descent;
45   GLuint font_dlist;
46   Bool clear_p;
47   char string[1024];
48
49   int last_ifps;
50   GLfloat last_fps;
51   int frame_count;
52   struct timeval prev, now;
53 };
54
55
56 static void
57 fps_init (ModeInfo *mi)
58 {
59   struct fps_state *st = mi->fps_state;
60
61   if (st) free (st);
62   st = (struct fps_state *) calloc (1, sizeof(*st));
63   mi->fps_state = st;
64
65   st->clear_p = get_boolean_resource (mi->dpy, "fpsSolid", "FPSSolid");
66
67 # ifndef HAVE_COCOA /* Xlib version */
68   {
69     const char *font;
70     XFontStruct *f;
71     Font id;
72     int first, last;
73
74     font = get_string_resource (mi->dpy, "fpsFont", "Font");
75
76     if (!font) font = "-*-courier-bold-r-normal-*-180-*";
77     f = XLoadQueryFont (mi->dpy, font);
78     if (!f) f = XLoadQueryFont (mi->dpy, "fixed");
79
80     id = f->fid;
81     first = f->min_char_or_byte2;
82     last = f->max_char_or_byte2;
83   
84     clear_gl_error ();
85     st->font_dlist = glGenLists ((GLuint) last+1);
86     check_gl_error ("glGenLists");
87
88     st->ascent = f->ascent;
89     st->descent = f->descent;
90
91     glXUseXFont (id, first, last-first+1, st->font_dlist + first);
92     check_gl_error ("glXUseXFont");
93   }
94
95 # else  /* HAVE_COCOA */
96
97   {
98     AGLContext ctx = aglGetCurrentContext();
99     GLint id    = 0;   /* 0 = system font; 1 = application font */
100     Style face  = 1;   /* 0 = plain; 1=B; 2=I; 3=BI; 4=U; 5=UB; etc. */
101     GLint size  = 24;
102     GLint first = 32;
103     GLint last  = 255;
104
105     st->ascent  = size * 0.9;
106     st->descent = size - st->ascent;
107
108     clear_gl_error ();
109     st->font_dlist = glGenLists ((GLuint) last+1);
110     check_gl_error ("glGenLists");
111
112     if (! aglUseFont (ctx, id, face, size, 
113                       first, last-first+1, st->font_dlist + first)) {
114       check_gl_error ("aglUseFont");
115       abort();
116     }
117   }
118
119 # endif  /* HAVE_COCOA */
120
121   st->x = 10;
122   st->y = 10;
123   if (get_boolean_resource (mi->dpy, "fpsTop", "FPSTop"))
124     st->y = - (st->ascent + 10);
125 }
126
127 void
128 fps_free (ModeInfo *mi)
129 {
130   if (mi->fps_state)
131     free (mi->fps_state);
132   mi->fps_state = 0;
133 }
134
135 static void
136 fps_print_string (ModeInfo *mi, GLfloat x, GLfloat y, const char *string)
137 {
138   struct fps_state *st = mi->fps_state;
139   const char *L2 = strchr (string, '\n');
140
141   if (y < 0)
142     {
143       y = mi->xgwa.height + y;
144       if (L2)
145         y -= (st->ascent + st->descent);
146     }
147
148 # ifdef DEBUG
149   clear_gl_error ();
150 # endif
151
152   /* Sadly, this causes a stall of the graphics pipeline (as would the
153      equivalent calls to glGet*.)  But there's no way around this, short
154      of having each caller set up the specific display matrix we need
155      here, which would kind of defeat the purpose of centralizing this
156      code in one file.
157    */
158   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
159                 GL_ENABLE_BIT |     /* for various glDisable calls */
160                 GL_CURRENT_BIT |    /* for glColor3f() */
161                 GL_LIST_BIT);       /* for glListBase() */
162   {
163 # ifdef DEBUG
164     check_gl_error ("glPushAttrib");
165 # endif
166
167     /* disable lighting and texturing when drawing bitmaps!
168        (glPopAttrib() restores these.)
169      */
170     glDisable (GL_TEXTURE_2D);
171     glDisable (GL_LIGHTING);
172     glDisable (GL_BLEND);
173     glDisable (GL_DEPTH_TEST);
174     glDisable (GL_CULL_FACE);
175
176     /* glPopAttrib() does not restore matrix changes, so we must
177        push/pop the matrix stacks to be non-intrusive there.
178      */
179     glMatrixMode(GL_PROJECTION);
180     glPushMatrix();
181     {
182 # ifdef DEBUG
183       check_gl_error ("glPushMatrix");
184 # endif
185       glLoadIdentity();
186
187       /* Each matrix mode has its own stack, so we need to push/pop
188          them separately. */
189       glMatrixMode(GL_MODELVIEW);
190       glPushMatrix();
191       {
192 # ifdef DEBUG
193         check_gl_error ("glPushMatrix");
194 # endif
195         glLoadIdentity();
196
197         gluOrtho2D(0, mi->xgwa.width, 0, mi->xgwa.height);
198 # ifdef DEBUG
199         check_gl_error ("gluOrtho2D");
200 # endif
201
202         /* clear the background */
203         if (st->clear_p)
204           {
205             int lines = L2 ? 2 : 1;
206             glColor3f (0, 0, 0);
207             glRecti (x / 2, y - st->descent,
208                      mi->xgwa.width - x,
209                      y + lines * (st->ascent + st->descent));
210           }
211
212         /* draw the text */
213         glColor3f (1, 1, 1);
214         glRasterPos2f (x, y);
215         glListBase (st->font_dlist);
216
217         if (L2)
218           {
219             L2++;
220             glCallLists (strlen(L2), GL_UNSIGNED_BYTE, L2);
221             glRasterPos2f (x, y + (st->ascent + st->descent));
222             glCallLists (L2 - string - 1, GL_UNSIGNED_BYTE, string);
223           }
224         else
225           {
226             glCallLists (strlen(string), GL_UNSIGNED_BYTE, string);
227           }
228
229 # ifdef DEBUG
230         check_gl_error ("fps_print_string");
231 # endif
232       }
233       glPopMatrix();
234     }
235     glMatrixMode(GL_PROJECTION);
236     glPopMatrix();
237
238   }
239   /* clean up after our state changes */
240   glPopAttrib();
241 # ifdef DEBUG
242   check_gl_error ("glPopAttrib");
243 # endif
244 }
245
246
247 GLfloat
248 fps_1 (ModeInfo *mi)
249 {
250   struct fps_state *st = mi->fps_state;
251   if (!st)
252     {
253       fps_init (mi);
254       st = mi->fps_state;
255       strcpy (st->string, "FPS: (accumulating...)");
256     }
257
258   /* Every N frames (where N is approximately one second's worth of frames)
259      check the wall clock.  We do this because checking the wall clock is
260      a slow operation.
261    */
262   if (st->frame_count++ >= st->last_ifps)
263     {
264 # ifdef GETTIMEOFDAY_TWO_ARGS
265       struct timezone tzp;
266       gettimeofday(&st->now, &tzp);
267 # else
268       gettimeofday(&st->now);
269 # endif
270
271       if (st->prev.tv_sec == 0)
272         st->prev = st->now;
273     }
274
275   /* If we've probed the wall-clock time, regenerate the string.
276    */
277   if (st->now.tv_sec != st->prev.tv_sec)
278     {
279       double uprev = st->prev.tv_sec + ((double) st->prev.tv_usec * 0.000001);
280       double unow  =  st->now.tv_sec + ((double)  st->now.tv_usec * 0.000001);
281       double fps   = st->frame_count / (unow - uprev);
282
283       st->prev = st->now;
284       st->frame_count = 0;
285       st->last_ifps = fps;
286       st->last_fps  = fps;
287
288       sprintf (st->string, "FPS: %.02f", fps);
289
290 #ifndef HAVE_COCOA
291       /* since there's no "-delay 0" in the Cocoa version,
292          don't bother mentioning the inter-frame delay. */
293       if (mi->pause != 0)
294         {
295           char buf[40];
296           sprintf(buf, "%f", mi->pause / 1000000.0); /* FTSO C */
297           while(*buf && buf[strlen(buf)-1] == '0')
298             buf[strlen(buf)-1] = 0;
299           if (buf[strlen(buf)-1] == '.')
300             buf[strlen(buf)-1] = 0;
301           sprintf(st->string + strlen(st->string),
302                   " (including %s sec/frame delay)",
303                   buf);
304         }
305 #endif /* HAVE_COCOA */
306
307       if (mi->polygon_count > 0)
308         {
309           unsigned long p = mi->polygon_count;
310           const char *s = "";
311 # if 0
312           if      (p >= (1024 * 1024)) p >>= 20, s = "M";
313           else if (p >= 2048)          p >>= 10, s = "K";
314 # endif
315
316           strcat (st->string, "\nPolys: ");
317           if (p >= 1000000)
318             sprintf (st->string + strlen(st->string), "%lu,%03lu,%03lu%s",
319                      (p / 1000000), ((p / 1000) % 1000), (p % 1000), s);
320           else if (p >= 1000)
321             sprintf (st->string + strlen(st->string), "%lu,%03lu%s",
322                      (p / 1000), (p % 1000), s);
323           else
324             sprintf (st->string + strlen(st->string), "%lu%s", p, s);
325         }
326     }
327
328   return st->last_fps;
329 }
330
331 void
332 fps_2 (ModeInfo *mi)
333 {
334   struct fps_state *st = mi->fps_state;
335   fps_print_string (mi, st->x, st->y, st->string);
336 }
337
338
339 void
340 do_fps (ModeInfo *mi)
341 {
342   fps_1 (mi);   /* Lazily compute current FPS value, about once a second. */
343   fps_2 (mi);   /* Print the string every frame (else nothing shows up.) */
344 }