http://packetstormsecurity.org/UNIX/admin/xscreensaver-4.14.tar.gz
[xscreensaver] / hacks / glx / glslideshow.c
1 /* glslideshow, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
2  * Loads a sequence of images and smoothly pans around them; crossfades
3  * when loading new images.
4  *
5  * First version Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
6  * based on flipscreen3d, Copyright (C) 2001 Ben Buxton (bb@cactii.net).
7  *
8  * Almost entirely rewritten by jwz, 21-Jun-2003.
9  *
10  * Permission to use, copy, modify, distribute, and sell this software and its
11  * documentation for any purpose is hereby granted without fee, provided that
12  * the above copyright notice appear in all copies and that both that
13  * copyright notice and this permission notice appear in supporting
14  * documentation.  No representations are made about the suitability of this
15  * software for any purpose.  It is provided "as is" without express or
16  * implied warranty.
17  *
18  */
19
20 #include <X11/Intrinsic.h>
21
22
23 # define PROGCLASS "GLSlideshow"
24 # define HACK_INIT init_slideshow
25 # define HACK_DRAW draw_slideshow
26 # define HACK_RESHAPE reshape_slideshow
27 # define HACK_HANDLE_EVENT glslideshow_handle_event
28 # define EVENT_MASK        (ExposureMask|VisibilityChangeMask)
29 # define slideshow_opts xlockmore_opts
30
31 # define DEF_FADE_DURATION  "2"
32 # define DEF_PAN_DURATION   "6"
33 # define DEF_IMAGE_DURATION "30"
34 # define DEF_ZOOM           "75"
35 # define DEF_FPS_CUTOFF     "5"
36 # define DEF_TITLES         "False"
37 # define DEF_DEBUG          "False"
38
39 #define DEFAULTS  "*delay:           20000                \n" \
40                   "*fadeDuration:  " DEF_FADE_DURATION   "\n" \
41                   "*panDuration:   " DEF_PAN_DURATION    "\n" \
42                   "*imageDuration: " DEF_IMAGE_DURATION  "\n" \
43                   "*zoom:          " DEF_ZOOM            "\n" \
44                   "*FPScutoff:     " DEF_FPS_CUTOFF      "\n" \
45                   "*debug   :      " DEF_DEBUG           "\n" \
46                   "*wireframe:       False                \n" \
47                   "*showFPS:         False                \n" \
48                   "*fpsSolid:        True                 \n" \
49                   "*titles:        " DEF_TITLES  "\n" \
50                   "*titleFont:       -*-times-bold-r-normal-*-180-*\n" \
51                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n"
52
53 # include "xlockmore.h"
54
55 #undef countof
56 #define countof(x) (sizeof((x))/sizeof((*x)))
57
58 #ifdef USE_GL
59
60 #include <GL/glu.h>
61 #include <math.h>
62 #include <sys/time.h>
63 #include <stdio.h>
64 #include <stdlib.h>
65 #include "grab-ximage.h"
66
67 typedef struct {
68   GLfloat x, y, w, h;
69 } rect;
70
71 typedef struct {
72   GLuint texid;                    /* which texture to draw */
73   enum { IN, OUT, DEAD } state;    /* how to draw it */
74   rect from, to;                   /* the journey this quad is taking */
75   char *title;
76 } gls_quad;
77
78
79 typedef struct {
80   GLXContext *glx_context;
81   time_t start_time;            /* when we started displaying this image */
82
83   int motion_frames;            /* how many frames each pan takes */
84   int fade_frames;              /* how many frames fading in/out takes */
85
86   gls_quad quads[2];            /* the (up to) 2 quads we animate */
87   GLuint texids[2];             /* textures: "old" and "new" */
88   GLuint current_texid;         /* the "new" one */
89
90   int img_w, img_h;             /* Size (pixels) of currently-loaded image */
91
92   double now;                   /* current time in seconds */
93   double pan_start_time;        /* when this pan began */
94   double image_start_time;      /* when this image was loaded */
95   double dawn_of_time;          /* when the program launched */
96
97   Bool redisplay_needed_p;      /* Sometimes we can get away with not
98                                    re-painting.  Tick this if a redisplay
99                                    is required. */
100
101   GLfloat fps;                  /* approximate frame rate we're achieving */
102   int pan_frame_count;          /* More frame-rate stats */
103   int fade_frame_count;
104   Bool low_fps_p;               /* Whether we have compensated for a low
105                                    frame rate. */
106
107   XFontStruct *xfont;
108   GLuint font_dlist;
109
110 } slideshow_state;
111
112 static slideshow_state *sss = NULL;
113
114
115 /* Command-line arguments
116  */
117 static int fade_seconds;    /* Duration in seconds of fade transitions.
118                                If 0, jump-cut instead of fading. */
119 static int pan_seconds;     /* Duration of each pan through an image. */
120 static int image_seconds;   /* How many seconds until loading a new image. */
121 static int zoom;            /* How far in to zoom when panning, in percent of
122                                image size: that is, 75 means "when zoomed all
123                                the way in, 75% of the image will be visible."
124                              */
125 static int fps_cutoff;      /* If the frame-rate falls below this, turn off
126                                zooming.*/
127 static Bool do_titles;      /* Display image titles. */
128 static Bool debug_p;        /* Be loud and do weird things. */
129
130
131 static XrmOptionDescRec opts[] = {
132   {"-fade",     ".slideshow.fadeDuration",  XrmoptionSepArg, 0     },
133   {"-pan",      ".slideshow.panDuration",   XrmoptionSepArg, 0     },
134   {"-duration", ".slideshow.imageDuration", XrmoptionSepArg, 0     },
135   {"-zoom",     ".slideshow.zoom",          XrmoptionSepArg, 0     },
136   {"-cutoff",   ".slideshow.FPScutoff",     XrmoptionSepArg, 0     },
137   {"-titles",   ".slideshow.titles",        XrmoptionNoArg, "True" },
138   {"+titles",   ".slideshow.titles",        XrmoptionNoArg, "True" },
139   {"-debug",    ".slideshow.debug",         XrmoptionNoArg, "True" },
140 };
141
142 static argtype vars[] = {
143   { &fade_seconds,  "fadeDuration", "FadeDuration", DEF_FADE_DURATION,  t_Int},
144   { &pan_seconds,   "panDuration",  "PanDuration",  DEF_PAN_DURATION,   t_Int},
145   { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
146   { &zoom,          "zoom",         "Zoom",         DEF_ZOOM,           t_Int},
147   { &fps_cutoff,    "FPScutoff",    "FPSCutoff",    DEF_FPS_CUTOFF,     t_Int},
148   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,         t_Bool},
149   { &do_titles,     "titles",       "Titles",       DEF_TITLES,        t_Bool},
150 };
151
152 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
153
154
155 /* Returns the current time in seconds as a double.
156  */
157 static double
158 double_time (void)
159 {
160   struct timeval now;
161 # ifdef GETTIMEOFDAY_TWO_ARGS
162   struct timezone tzp;
163   gettimeofday(&now, &tzp);
164 # else
165   gettimeofday(&now);
166 # endif
167
168   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
169 }
170
171
172 static void
173 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
174 {
175   const char *font = get_string_resource (res, "Font");
176   XFontStruct *f;
177   Font id;
178   int first, last;
179
180   if (!font) font = "-*-times-bold-r-normal-*-180-*";
181
182   f = XLoadQueryFont(mi->dpy, font);
183   if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
184
185   id = f->fid;
186   first = f->min_char_or_byte2;
187   last = f->max_char_or_byte2;
188   
189   clear_gl_error ();
190   *dlistP = glGenLists ((GLuint) last+1);
191   check_gl_error ("glGenLists");
192   glXUseXFont(id, first, last-first+1, *dlistP + first);
193   check_gl_error ("glXUseXFont");
194
195   *fontP = f;
196 }
197
198
199 static void
200 print_title_string (ModeInfo *mi, const char *string, GLfloat x, GLfloat y)
201 {
202   slideshow_state *ss = &sss[MI_SCREEN(mi)];
203   XFontStruct *font = ss->xfont;
204   GLfloat line_height = font->ascent + font->descent;
205
206   y -= line_height;
207
208   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
209                 GL_ENABLE_BIT);     /* for various glDisable calls */
210   glDisable (GL_LIGHTING);
211   glDisable (GL_DEPTH_TEST);
212   {
213     glMatrixMode(GL_PROJECTION);
214     glPushMatrix();
215     {
216       glLoadIdentity();
217
218       glMatrixMode(GL_MODELVIEW);
219       glPushMatrix();
220       {
221         unsigned int i;
222         int x2 = x;
223         glLoadIdentity();
224
225         gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
226
227         glRasterPos2f (x, y);
228         for (i = 0; i < strlen(string); i++)
229           {
230             char c = string[i];
231             if (c == '\n')
232               {
233                 glRasterPos2f (x, (y -= line_height));
234                 x2 = x;
235               }
236             else
237               {
238                 glCallList (ss->font_dlist + (int)(c));
239                 x2 += (font->per_char
240                        ? font->per_char[c - font->min_char_or_byte2].width
241                        : font->min_bounds.width);
242               }
243           }
244       }
245       glPopMatrix();
246     }
247     glMatrixMode(GL_PROJECTION);
248     glPopMatrix();
249   }
250   glPopAttrib();
251
252   glMatrixMode(GL_MODELVIEW);
253 }
254
255
256 static void
257 draw_quad (ModeInfo *mi, gls_quad *q)
258 {
259   slideshow_state *ss = &sss[MI_SCREEN(mi)];
260   int wire = MI_IS_WIREFRAME(mi);
261   GLfloat ratio;
262   rect current;
263   GLfloat opacity;
264   double secs;
265   GLfloat texw = 0;
266   GLfloat texh = 0;
267
268   if (q->state == DEAD)
269     return;
270
271   secs = ss->now - ss->pan_start_time;
272
273   if (q->state == OUT)
274     secs += pan_seconds;
275
276   ratio = secs / (pan_seconds + fade_seconds);
277
278   current.x = q->from.x + ratio * (q->to.x - q->from.x);
279   current.y = q->from.y + ratio * (q->to.y - q->from.y);
280   current.w = q->from.w + ratio * (q->to.w - q->from.w);
281   current.h = q->from.h + ratio * (q->to.h - q->from.h);
282
283   if (secs < fade_seconds)
284     opacity = secs / (GLfloat) fade_seconds;    /* fading in or out... */
285   else if (secs < pan_seconds)
286     opacity = 1;                                /* panning opaquely. */
287   else
288     opacity = 1 - ((secs - pan_seconds) /
289                    (GLfloat) fade_seconds);    /* fading in or out... */
290
291   if (q->state == OUT && opacity < 0.0001)
292     q->state = DEAD;
293
294   glPushMatrix();
295
296   glTranslatef (current.x, current.y, 0);
297   glScalef (current.w, current.h, 1);
298
299   if (!wire)
300     {
301       texw = mi->xgwa.width  / (GLfloat) ss->img_w;
302       texh = mi->xgwa.height / (GLfloat) ss->img_h;
303
304       glEnable (GL_TEXTURE_2D);
305       glEnable (GL_BLEND);
306       glBindTexture (GL_TEXTURE_2D, q->texid);
307       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
308       glDepthMask (GL_FALSE);
309
310       /* Draw the texture quad
311        */
312       glColor4f (1, 1, 1, opacity);
313       glNormal3f (0, 0, 1);
314       glBegin (GL_QUADS);
315       glTexCoord2f (0,    0);    glVertex3f (0, 0, 0);
316       glTexCoord2f (0,    texh); glVertex3f (0, 1, 0);
317       glTexCoord2f (texw, texh); glVertex3f (1, 1, 0);
318       glTexCoord2f (texw, 0);    glVertex3f (1, 0, 0);
319       glEnd();
320
321       glDisable (GL_TEXTURE_2D);
322       glDisable (GL_BLEND);
323     }
324
325   if (wire)
326     glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
327                (q->texid == ss->texids[0] ? 0 : opacity),
328                opacity);
329   else
330     glColor4f (1, 1, 1, opacity);
331
332
333   /* Draw a grid inside the box
334    */
335   if (wire)
336     {
337       GLfloat d = 0.1;
338       GLfloat x, y;
339       glBegin(GL_LINES);
340       glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
341       glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
342
343       for (y = 0; y < 1+d; y += d)
344         for (x = 0; x < 1+d; x += d)
345           {
346             glVertex3f (0, y, 0); glVertex3f (1, y, 0);
347             glVertex3f (x, 0, 0); glVertex3f (x, 1, 0);
348           }
349       glEnd();
350     }
351
352   if (do_titles &&
353       q->state != DEAD &&
354       q->title && *q->title)
355     {
356       /* #### this is wrong -- I really want to draw this with
357          "1,1,1,opacity", so that the text gets laid down on top
358          of the image with alpha, but that doesn't work, and I
359          don't know why...
360        */
361       glColor4f (opacity, opacity, opacity, 1);
362       print_title_string (mi, q->title,
363                           10, mi->xgwa.height - 10);
364     }
365
366   glPopMatrix();
367
368   if (debug_p)
369     {
370       /* Draw the "from" and "to" boxes
371        */
372       glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
373                  (q->texid == ss->texids[0] ? 0 : opacity),
374                  opacity);
375
376       glBegin (GL_LINE_LOOP);
377       glVertex3f (q->from.x,             q->from.y,             0);
378       glVertex3f (q->from.x + q->from.w, q->from.y,             0);
379       glVertex3f (q->from.x + q->from.w, q->from.y + q->from.h, 0);
380       glVertex3f (q->from.x,             q->from.y + q->from.h, 0);
381       glEnd();
382
383       glBegin (GL_LINE_LOOP);
384       glVertex3f (q->to.x,               q->to.y,               0);
385       glVertex3f (q->to.x + q->to.w,     q->to.y,               0);
386       glVertex3f (q->to.x + q->to.w,     q->to.y + q->to.h,     0);
387       glVertex3f (q->to.x,               q->to.y + q->to.h,     0);
388       glEnd();
389     }
390 }
391
392
393 static void
394 draw_quads (ModeInfo *mi)
395 {
396   slideshow_state *ss = &sss[MI_SCREEN(mi)];
397   GLfloat s, o;
398   int i;
399
400   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
401
402   glPushMatrix();
403
404   s = (100.0 / zoom);
405   o = (1-s)/2;
406   glTranslatef (o, o, 0);
407   glScalef (s, s, s);
408
409   for (i = 0; i < countof(ss->quads); i++)
410     draw_quad (mi, &ss->quads[i]);
411
412   glPopMatrix();
413
414   if (debug_p)
415     {
416       glColor4f (1, 1, 1, 1);
417       glBegin (GL_LINE_LOOP);
418       glVertex3f (0, 0, 0);
419       glVertex3f (0, 1, 0);
420       glVertex3f (1, 1, 0);
421       glVertex3f (1, 0, 0);
422       glEnd();
423     }
424 }
425
426
427 /* Re-randomize the state of the given quad.
428  */
429 static void
430 reset_quad (ModeInfo *mi, gls_quad *q)
431 {
432 /*  slideshow_state *ss = &sss[MI_SCREEN(mi)];*/
433
434   GLfloat mid_w = (zoom / 100.0);
435   GLfloat mid_h = (zoom / 100.0);
436   GLfloat mid_x = (1 - mid_w) / 2;
437   GLfloat mid_y = (1 - mid_h) / 2;
438
439   GLfloat small = mid_w + frand ((1 - mid_w) * 0.3);
440 #if 0
441   GLfloat large = small + frand ((1 - small) / 2) + ((1 - small) / 2);
442 #else
443   GLfloat large = small + frand (1 - small);
444 #endif
445
446   if (q->state != DEAD)
447     abort();    /* we should only be resetting a quad when it's not visible. */
448
449   /* Possible box sizes range between "zoom" and "100%".
450      Pick a small box size, and a large box size.
451      Assign each a random position within the 1x1 box,
452      such that they encompass the middle "zoom" percentage.
453      One of those is the start, and one is the end.
454      Each frame will transition between one and the other.
455    */
456
457   if (random() & 1)
458     {
459       q->from.w = small; q->from.h = small;
460       q->to.w   = large; q->to.h   = large;
461     }
462   else
463     {
464       q->from.w = large; q->from.h = large;
465       q->to.w   = small; q->to.h   = small;
466     }
467
468   q->from.x = mid_x - frand (q->from.w - mid_w);
469   q->from.y = mid_y - frand (q->from.h - mid_h);
470   q->to.x   = mid_x - frand (q->to.w - mid_w);
471   q->to.y   = mid_y - frand (q->to.w - mid_h);
472
473   q->state = IN;
474 }
475
476
477 /* Shrinks the XImage by a factor of two.
478  */
479 static void
480 shrink_image (ModeInfo *mi, XImage *ximage)
481 {
482   int w2 = ximage->width/2;
483   int h2 = ximage->height/2;
484   int x, y;
485   XImage *ximage2;
486
487   if (w2 <= 32 || h2 <= 32)   /* let's not go crazy here, man. */
488     return;
489
490   if (debug_p)
491     fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
492              progname, ximage->width, ximage->height, w2, h2);
493
494   ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
495                           32, ZPixmap, 0, 0,
496                           w2, h2, 32, 0);
497   ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
498   if (!ximage2->data)
499     {
500       fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
501                progname, ximage->width, ximage->height, w2, h2);
502       exit (1);
503     }
504   for (y = 0; y < h2; y++)
505     for (x = 0; x < w2; x++)
506       XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
507   free (ximage->data);
508   *ximage = *ximage2;
509   ximage2->data = 0;
510   XFree (ximage2);
511 }
512
513
514 /* Load a new image into a texture for the given quad.
515  */
516 static void
517 load_quad (ModeInfo *mi, gls_quad *q)
518 {
519   slideshow_state *ss = &sss[MI_SCREEN(mi)];
520   XImage *ximage;
521   int status;
522   int max_reduction = 7;
523   int err_count = 0;
524   int wire = MI_IS_WIREFRAME(mi);
525
526   if (q->state != DEAD) abort();
527
528   /* Figure out which texid is currently in use, and pick the other one.
529    */
530   {
531     GLuint tid = 0;
532     int i;
533     if (ss->current_texid == 0)
534       tid = ss->texids[0];
535     else
536       for (i = 0; i < countof(ss->texids); i++)
537         if (ss->texids[i] != ss->current_texid)
538           {
539             tid = ss->texids[i];
540             break;
541           }
542
543     if (tid == 0) abort();   /* both textures in use by visible quads? */
544     q->texid = tid;
545     ss->current_texid = tid;
546   }
547
548   if (debug_p)
549     fprintf (stderr, "%s: debug: loading image %d (%dx%d)\n",
550              progname, q->texid, mi->xgwa.width, mi->xgwa.height);
551
552   if (wire)
553     goto DONE;
554
555   if (q->title) free (q->title);
556   q->title = 0;
557   ximage = screen_to_ximage (mi->xgwa.screen, mi->window, &q->title);
558
559   if (q->title)   /* strip filename to part after last /. */
560     {
561       char *s = strrchr (q->title, '/');
562       if (s) strcpy (q->title, s+1);
563     }
564
565   if (debug_p)
566     fprintf (stderr, "%s: debug: loaded image %d (%s)\n",
567              progname, q->texid, (q->title ? q->title : "(null)"));
568
569   glBindTexture (GL_TEXTURE_2D, q->texid);
570   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
571   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
572                    GL_LINEAR_MIPMAP_LINEAR);
573   
574   ss->img_w = ximage->width;
575   ss->img_h = ximage->height;
576
577  AGAIN:
578
579   clear_gl_error();
580   status = gluBuild2DMipmaps (GL_TEXTURE_2D, 3,
581                               ximage->width, ximage->height,
582                               GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
583   
584   if(!status && glGetError())
585    /* Some implementations of gluBuild2DMipmaps(), but set a GL error anyway.
586       We could just call check_gl_error(), but that would exit. */
587     status = -1;
588
589   if (status)
590     {
591       char buf[100];
592       const char *s = (char *) gluErrorString (status);
593
594       if (!s || !*s)
595         {
596           sprintf (buf, "unknown error %d", status);
597           s = buf;
598         }
599
600       clear_gl_error();
601
602       if (++err_count > max_reduction)
603         {
604           fprintf(stderr,
605                   "\n"
606                   "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
607                   "%s: GLU said: \"%s\".\n"
608                   "%s: probably this means "
609                   "\"your video card is worthless and weak\"?\n\n",
610                   progname, MI_WIDTH(mi), MI_HEIGHT(mi),
611                   ximage->width, ximage->height,
612                   progname, s,
613                   progname);
614           exit (1);
615         }
616       else
617         {
618           if (debug_p)
619             fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
620                      progname, ximage->width, ximage->height, s);
621           shrink_image (mi, ximage);
622           goto AGAIN;
623         }
624     }
625
626   check_gl_error("mipmapping");  /* should get a return code instead of a
627                                     GL error, but just in case... */
628
629   free(ximage->data);
630   ximage->data = 0;
631   XDestroyImage(ximage);
632
633  DONE:
634
635   /* Re-set "now" so that time spent loading the image file does not count
636      against the time remaining in this stage of the animation: image loading,
637      if it takes a perceptible amount of time, will cause the animation to
638      pause, but will not cause it to drop frames.
639    */
640   ss->now = double_time ();
641   ss->image_start_time = ss->now;
642
643   ss->redisplay_needed_p = True;
644 }
645
646
647 void
648 reshape_slideshow (ModeInfo *mi, int width, int height)
649 {
650   slideshow_state *ss = &sss[MI_SCREEN(mi)];
651   GLfloat s;
652   glViewport (0, 0, width, height);
653   glMatrixMode (GL_PROJECTION);
654   glLoadIdentity();
655   glMatrixMode (GL_MODELVIEW);
656   glLoadIdentity();
657
658   s = 2;
659
660   if (debug_p)
661     {
662       s *= (zoom / 100.0) * 0.75;
663       if (s < 0.1) s = 0.1;
664     }
665
666   glScalef (s, s, s);
667   glTranslatef (-0.5, -0.5, 0);
668
669   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
670
671   ss->redisplay_needed_p = True;
672 }
673
674
675 Bool
676 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
677 {
678   slideshow_state *ss = &sss[MI_SCREEN(mi)];
679
680   if (event->xany.type == Expose ||
681       event->xany.type == GraphicsExpose ||
682       event->xany.type == VisibilityNotify)
683     {
684       if (debug_p)
685         fprintf (stderr, "%s: debug: exposure\n", progname);
686       ss->redisplay_needed_p = True;
687       return True;
688     }
689   return False;
690 }
691
692
693 /* Do some sanity checking on various user-supplied values, and make
694    sure they are all internally consistent.
695  */
696 static void
697 sanity_check (ModeInfo *mi)
698 {
699   if (zoom < 1) zoom = 1;           /* zoom is a positive percentage */
700   else if (zoom > 100) zoom = 100;
701
702   if (pan_seconds < fade_seconds)   /* pan is inclusive of fade */
703     pan_seconds = fade_seconds;
704
705   if (pan_seconds == 0)             /* no zero-length cycles, please... */
706     pan_seconds = 1;
707
708   if (image_seconds < pan_seconds)  /* we only change images at fade-time */
709     image_seconds = pan_seconds;
710
711   /* If we're not panning/zooming within the image, then there's no point
712      in crossfading the image with itself -- only do crossfades when changing
713      to a new image. */
714   if (zoom == 100 && pan_seconds < image_seconds)
715     pan_seconds = image_seconds;
716
717   if      (fps_cutoff < 0)  fps_cutoff = 0;
718   else if (fps_cutoff > 30) fps_cutoff = 30;
719 }
720
721
722 void
723 init_slideshow (ModeInfo *mi)
724 {
725   int screen = MI_SCREEN(mi);
726   slideshow_state *ss;
727   int wire = MI_IS_WIREFRAME(mi);
728   int i;
729   
730   if (sss == NULL) {
731     if ((sss = (slideshow_state *)
732          calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
733       return;
734   }
735   ss = &sss[screen];
736
737   if ((ss->glx_context = init_GL(mi)) != NULL) {
738     reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
739   } else {
740     MI_CLEARWINDOW(mi);
741   }
742
743   sanity_check(mi);
744
745   if (debug_p)
746     fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
747              progname, pan_seconds, fade_seconds, image_seconds, zoom);
748
749   if (! wire)
750     {
751       glShadeModel (GL_SMOOTH);
752       glPolygonMode (GL_FRONT_AND_BACK,GL_FILL);
753       glEnable (GL_DEPTH_TEST);
754       glEnable (GL_CULL_FACE);
755       glCullFace (GL_FRONT);
756       glDisable (GL_LIGHTING);
757     }
758
759   ss->now = double_time ();
760   ss->dawn_of_time = ss->now;
761
762   if (debug_p) glLineWidth (3);
763
764   ss->pan_start_time   = ss->now;
765   ss->image_start_time = ss->now;
766
767   load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
768
769   for (i = 0; i < countof(ss->texids); i++)
770     glGenTextures (1, &ss->texids[i]);
771   ss->current_texid = 0;
772
773   for (i = 0; i < countof(ss->quads); i++)
774     {
775       gls_quad *q = &ss->quads[i];
776       q->texid = ss->current_texid;
777       q->state = DEAD;
778       reset_quad (mi, q);
779       q->state = DEAD;
780     }
781
782   load_quad (mi, &ss->quads[0]);
783   ss->quads[0].state = IN;
784
785   ss->redisplay_needed_p = True;
786 }
787
788
789 /* Call this each time we change from one state to another.
790    It gathers statistics on the frame rate of the previous state,
791    and if it's bad, turn things off (under the assumption that
792    we're running on sucky hardware.)
793  */
794 static void
795 ponder_state_change (ModeInfo *mi)
796 {
797   slideshow_state *ss = &sss[MI_SCREEN(mi)];
798   const char *which;
799   int frames, secs;
800   GLfloat fps;
801
802   if (ss->fade_frame_count && ss->pan_frame_count)
803     abort();  /* one of these should be zero! */
804   else if (ss->fade_frame_count)   /* just finished fading */
805     {
806       which = "faded ";
807       secs = fade_seconds;
808       frames = ss->fade_frame_count;
809       ss->fade_frame_count = 0;
810     }
811   else if (ss->pan_frame_count)   /* just finished panning */
812     {
813       which = "panned";
814       secs = pan_seconds;
815       frames = ss->pan_frame_count;
816       ss->pan_frame_count = 0;
817     }
818   else
819     abort();  /* one of these should be non-zero! */
820
821   fps = frames / (GLfloat) secs;
822
823   if (debug_p)
824     fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
825              progname, which, frames, secs, fps);
826
827
828   if (fps < fps_cutoff && !ss->low_fps_p)   /* oops, this computer sucks! */
829     {
830       int i;
831
832       fprintf (stderr,
833                "%s: frame rate is only %.1f!  "
834                "Turning off pan/fade to compensate...\n",
835                progname, fps);
836       zoom = 100;
837       fade_seconds = 0;
838       ss->low_fps_p = True;
839
840       sanity_check (mi);
841
842       /* Reset all quads, and mark only #0 as active. */
843       for (i = 0; i < countof(ss->quads); i++)
844         {
845           gls_quad *q = &ss->quads[i];
846           q->state = DEAD;
847           reset_quad (mi, q);
848           q->texid = ss->current_texid;
849           q->state = (i == 0 ? IN : DEAD);
850         }
851
852       ss->pan_start_time = ss->now;
853       ss->redisplay_needed_p = True;
854
855       /* Need this in case zoom changed. */
856       reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
857     }
858 }
859
860
861 void
862 draw_slideshow (ModeInfo *mi)
863 {
864   slideshow_state *ss = &sss[MI_SCREEN(mi)];
865   Window w = MI_WINDOW(mi);
866   double secs;
867
868   if (!ss->glx_context)
869     return;
870
871   if (zoom < 100)
872     ss->redisplay_needed_p = True;
873
874   /* States:
875       0: - A invisible,  B invisible
876          - A fading in,  B invisible
877
878       1: - A opaque,     B invisible
879          - A fading out, B fading in
880          - A invisible, gets reset
881          - A invisible,  B opaque
882
883       2: - A invisible,  B opaque
884          - A fading in,  B fading out
885          - B invisible, gets reset
886          - A opaque,     B invisible (goto 1)
887   */
888
889   ss->now = double_time();
890
891   secs = ss->now - ss->pan_start_time;
892
893   if (secs < fade_seconds)
894     {
895       /* We are in the midst of a fade:
896          one quad is fading in, the other is fading out.
897          (If this is the very first time, then the one
898          fading out is already out.)
899        */
900       ss->redisplay_needed_p = True;
901       ss->fade_frame_count++;
902
903       if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
904              (ss->quads[1].state == IN && ss->quads[0].state == OUT) ||
905              (ss->quads[0].state == IN && ss->quads[1].state == DEAD)))
906         abort();
907     }
908   else if (secs < pan_seconds)
909     {
910       /* One quad is visible and in motion, the other is not.
911       */
912       if (ss->fade_frame_count != 0)  /* we just switched from fade to pan */
913         ponder_state_change (mi);
914       ss->pan_frame_count++;
915     }
916   else
917     {
918       /* One quad is visible and in motion, the other is not.
919          It's time to begin fading the visible one out, and the
920          invisible one in.  (Reset the invisible one first.)
921        */
922       gls_quad *vq, *iq;
923
924       ponder_state_change (mi);
925
926       if (ss->quads[0].state == IN)
927         {
928           vq = &ss->quads[0];
929           iq = &ss->quads[1];
930         }
931       else
932         {
933           vq = &ss->quads[1];
934           iq = &ss->quads[0];
935         }
936
937       if (vq->state != IN)   abort();
938
939       /* I don't understand why sometimes iq is still OUT and not DEAD. */
940       if (iq->state == OUT)  iq->state = DEAD;
941       if (iq->state != DEAD) abort();
942
943       vq->state = OUT;
944
945       if (ss->image_start_time + image_seconds <= ss->now)
946         load_quad (mi, iq);
947
948       reset_quad (mi, iq);               /* fade invisible in */
949       iq->texid = ss->current_texid;     /* make sure we're using latest img */
950
951       ss->pan_start_time = ss->now;
952
953       if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
954              (ss->quads[1].state == IN && ss->quads[0].state == OUT)))
955         abort();
956     }
957
958   ss->fps = fps_1 (mi);
959
960   if (!ss->redisplay_needed_p)
961     return;
962   else if (debug_p && zoom == 100)
963     fprintf (stderr, "%s: debug: drawing (%d)\n", progname,
964              (int) (ss->now - ss->dawn_of_time));
965
966   draw_quads (mi);
967   ss->redisplay_needed_p = False;
968
969   if (mi->fps_p) fps_2(mi);
970
971   glFinish();
972   glXSwapBuffers (MI_DISPLAY (mi), w);
973 }
974
975 #endif /* USE_GL */