http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / glslideshow.c
1 /* glslideshow, Copyright (c) 2003-2005 Jamie Zawinski <jwz@jwz.org>
2  * Loads a sequence of images and smoothly pans around them; crossfades
3  * when loading new images.
4  *
5  * Originally written by Mike Oliphant <oliphant@gtk.org> (c) 2002, 2003.
6  * Rewritten by jwz, 21-Jun-2003.
7  * Rewritten by jwz again, 6-Feb-2005.
8  *
9  * Permission to use, copy, modify, distribute, and sell this software and its
10  * documentation for any purpose is hereby granted without fee, provided that
11  * the above copyright notice appear in all copies and that both that
12  * copyright notice and this permission notice appear in supporting
13  * documentation.  No representations are made about the suitability of this
14  * software for any purpose.  It is provided "as is" without express or
15  * implied warranty.
16  *
17  *****************************************************************************
18  *
19  * TODO:
20  *
21  * - When a new image is loaded, there is a glitch: animation pauses during
22  *   the period when we're loading the image-to-fade-in.  On fast (2GHz)
23  *   machines, this stutter is short but noticable (usually around 1/10th
24  *   second.)  On slower machines, it can be much more pronounced.
25  *   This turns out to be hard to fix...
26  *
27  *   Image loading happens in three stages:
28  *
29  *    1: Fork a process and run xscreensaver-getimage in the background.
30  *       This writes image data to a server-side X pixmap.
31  *
32  *    2: When that completes, a callback informs us that the pixmap is ready.
33  *       We must then download the pixmap data from the server with XGetImage
34  *       (or XShmGetImage.)
35  *
36  *    3: Once we have the bits, we must convert them from server-native bitmap
37  *       layout to 32 bit RGBA in client-endianness, to make them usable as
38  *       OpenGL textures.
39  *
40  *    4: We must actually construct a texture.
41  *
42  *   So, the speed of step 1 doesn't really matter, since that happens in
43  *   the background.  But steps 2, 3, and 4 happen in *this* process, and
44  *   cause the visible glitch.
45  *
46  *   Step 2 can't be moved to another process without opening a second
47  *   connection to the X server, which is pretty heavy-weight.  (That would
48  *   be possible, though; the other process could open an X connection,
49  *   retrieve the pixmap, and feed it back to us through a pipe or
50  *   something.)
51  *
52  *   Step 3 might be able to be optimized by coding tuned versions of
53  *   grab-ximage.c:copy_ximage() for the most common depths and bit orders.
54  *   (Or by moving it into the other process along with step 2.)
55  *
56  *   Step 4 is the hard one, though.  It might be possible to speed up this
57  *   step if there is some way to allow two GL processes share texture
58  *   data.  Unless, of course, all the time being consumed by step 4 is
59  *   because the graphics pipeline is flooded, in which case, that other
60  *   process would starve the screen anyway.
61  *
62  *   Is it possible to use a single GLX context in a multithreaded way?
63  *   Or use a second GLX context, but allow the two contexts to share data?
64  *   I can't find any documentation about this.
65  *
66  *   How does Apple do this with their MacOSX slideshow screen saver?
67  *   Perhaps it's easier for them because their OpenGL libraries have
68  *   thread support at a lower level?
69  */
70
71 #include <X11/Intrinsic.h>
72
73 # define PROGCLASS "GLSlideshow"
74 # define HACK_INIT init_slideshow
75 # define HACK_DRAW draw_slideshow
76 # define HACK_RESHAPE reshape_slideshow
77 # define HACK_HANDLE_EVENT glslideshow_handle_event
78 # define EVENT_MASK        (ExposureMask|VisibilityChangeMask)
79 # define slideshow_opts xlockmore_opts
80
81 # define DEF_FADE_DURATION  "2"
82 # define DEF_PAN_DURATION   "6"
83 # define DEF_IMAGE_DURATION "30"
84 # define DEF_ZOOM           "75"
85 # define DEF_FPS_CUTOFF     "5"
86 # define DEF_TITLES         "False"
87 # define DEF_LETTERBOX      "True"
88 # define DEF_DEBUG          "False"
89 # define DEF_MIPMAP         "True"
90
91 #define DEFAULTS  "*delay:           20000                \n" \
92                   "*fadeDuration:  " DEF_FADE_DURATION   "\n" \
93                   "*panDuration:   " DEF_PAN_DURATION    "\n" \
94                   "*imageDuration: " DEF_IMAGE_DURATION  "\n" \
95                   "*zoom:          " DEF_ZOOM            "\n" \
96                   "*titles:        " DEF_TITLES          "\n" \
97                   "*FPScutoff:     " DEF_FPS_CUTOFF      "\n" \
98                   "*letterbox:     " DEF_LETTERBOX       "\n" \
99                   "*debug:         " DEF_DEBUG           "\n" \
100                   "*mipmap:        " DEF_MIPMAP          "\n" \
101                   "*wireframe:       False                \n" \
102                   "*showFPS:         False                \n" \
103                   "*fpsSolid:        True                 \n" \
104                   "*useSHM:          True                 \n" \
105                   "*titleFont:       -*-times-bold-r-normal-*-180-*\n" \
106                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n"
107
108 # include "xlockmore.h"
109
110 #undef countof
111 #define countof(x) (sizeof((x))/sizeof((*x)))
112
113 #ifdef USE_GL
114
115 #include <GL/glu.h>
116 #include <math.h>
117 #include <sys/time.h>
118 #include <stdio.h>
119 #include <stdlib.h>
120 #include "grab-ximage.h"
121 #include "glxfonts.h"
122
123 extern XtAppContext app;
124
125 typedef struct {
126   double x, y, w, h;
127 } rect;
128
129 typedef struct {
130   ModeInfo *mi;
131   int id;                          /* unique number for debugging */
132   char *title;                     /* the filename of this image */
133   int w, h;                        /* size in pixels of the image */
134   int tw, th;                      /* size in pixels of the texture */
135   XRectangle geom;                 /* where in the image the bits are */
136   Bool loaded_p;                   /* whether the image has finished loading */
137   Bool used_p;                     /* whether the image has yet appeared
138                                       on screen */
139   GLuint texid;                    /* which texture contains the image */
140   int refcount;                    /* how many sprites refer to this image */
141 } image;
142
143
144 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
145
146 typedef struct {
147   int id;                          /* unique number for debugging */
148   image *img;                      /* which image this animation displays */
149   GLfloat opacity;                 /* how to render it */
150   double start_time;               /* when this animation began */
151   rect from, to, current;          /* the journey this image is taking */
152   sprite_state state;              /* the state we're in right now */
153   sprite_state prev_state;         /* the state we were in previously */
154   double state_time;               /* time of last state change */
155   int frame_count;                 /* frames since last state change */
156 } sprite;
157
158
159 typedef struct {
160   GLXContext *glx_context;
161   int nimages;                  /* how many images are loaded or loading now */
162   image *images[10];            /* pointers to the images */
163
164   int nsprites;                 /* how many sprites are animating right now */
165   sprite *sprites[10];          /* pointers to the live sprites */
166
167   double now;                   /* current time in seconds */
168   double dawn_of_time;          /* when the program launched */
169   double image_load_time;       /* time when we last loaded a new image */
170   double prev_frame_time;       /* time when we last drew a frame */
171
172   Bool redisplay_needed_p;      /* Sometimes we can get away with not
173                                    re-painting.  Tick this if a redisplay
174                                    is required. */
175   Bool change_now_p;            /* Set when the user clicks to ask for a new
176                                    image right now. */
177
178   GLfloat fps;                  /* approximate frame rate we're achieving */
179   GLfloat theoretical_fps;      /* maximum frame rate that might be possible */
180   Bool checked_fps_p;           /* Whether we have checked for a low
181                                    frame rate. */
182
183   XFontStruct *xfont;           /* for printing image file names */
184   GLuint font_dlist;
185
186   int sprite_id, image_id;      /* debugging id counters */
187
188 } slideshow_state;
189
190 static slideshow_state *sss = NULL;
191
192
193 /* Command-line arguments
194  */
195 static int fade_seconds;    /* Duration in seconds of fade transitions.
196                                If 0, jump-cut instead of fading. */
197 static int pan_seconds;     /* Duration of each pan through an image. */
198 static int image_seconds;   /* How many seconds until loading a new image. */
199 static int zoom;            /* How far in to zoom when panning, in percent of
200                                image size: that is, 75 means "when zoomed all
201                                the way in, 75% of the image will be visible."
202                              */
203 static int fps_cutoff;      /* If the frame-rate falls below this, turn off
204                                zooming.*/
205 static Bool letterbox_p;    /* When a loaded image is not the same aspect
206                                ratio as the window, whether to display black
207                                bars.
208                              */
209 static Bool mipmap_p;       /* Use mipmaps instead of single textures. */
210 static Bool do_titles;      /* Display image titles. */
211 static Bool debug_p;        /* Be loud and do weird things. */
212
213
214 static XrmOptionDescRec opts[] = {
215   {"-fade",         ".fadeDuration",  XrmoptionSepArg, 0      },
216   {"-pan",          ".panDuration",   XrmoptionSepArg, 0      },
217   {"-duration",     ".imageDuration", XrmoptionSepArg, 0      },
218   {"-zoom",         ".zoom",          XrmoptionSepArg, 0      },
219   {"-cutoff",       ".FPScutoff",     XrmoptionSepArg, 0      },
220   {"-titles",       ".titles",        XrmoptionNoArg, "True"  },
221   {"-letterbox",    ".letterbox",     XrmoptionNoArg, "True"  },
222   {"-clip",         ".letterbox",     XrmoptionNoArg, "False"  },
223   {"-mipmaps",      ".mipmap",        XrmoptionNoArg, "True"  },
224   {"-no-mipmaps",   ".mipmap",        XrmoptionNoArg, "False" },
225   {"-debug",        ".debug",         XrmoptionNoArg, "True"  },
226 };
227
228 static argtype vars[] = {
229   { &fade_seconds,  "fadeDuration", "FadeDuration", DEF_FADE_DURATION,  t_Int},
230   { &pan_seconds,   "panDuration",  "PanDuration",  DEF_PAN_DURATION,   t_Int},
231   { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
232   { &zoom,          "zoom",         "Zoom",         DEF_ZOOM,           t_Int},
233   { &mipmap_p,      "mipmap",       "Mipmap",       DEF_MIPMAP,        t_Bool},
234   { &letterbox_p,   "letterbox",    "Letterbox",    DEF_LETTERBOX,     t_Bool},
235   { &fps_cutoff,    "FPScutoff",    "FPSCutoff",    DEF_FPS_CUTOFF,     t_Int},
236   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,         t_Bool},
237   { &do_titles,     "titles",       "Titles",       DEF_TITLES,        t_Bool},
238 };
239
240 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
241
242
243 static const char *
244 blurb (void)
245 {
246   static char buf[255];
247   time_t now = time ((time_t *) 0);
248   char *ct = (char *) ctime (&now);
249   int n = strlen(progname);
250   if (n > 100) n = 99;
251   strncpy(buf, progname, n);
252   buf[n++] = ':';
253   buf[n++] = ' ';
254   strncpy(buf+n, ct+11, 8);
255   strcpy(buf+n+9, ": ");
256   return buf;
257 }
258
259
260 /* Returns the current time in seconds as a double.
261  */
262 static double
263 double_time (void)
264 {
265   struct timeval now;
266 # ifdef GETTIMEOFDAY_TWO_ARGS
267   struct timezone tzp;
268   gettimeofday(&now, &tzp);
269 # else
270   gettimeofday(&now);
271 # endif
272
273   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
274 }
275
276
277 static void image_loaded_cb (const char *filename, XRectangle *geom,
278                              int image_width, int image_height,
279                              int texture_width, int texture_height,
280                              void *closure);
281
282
283 /* Allocate an image structure and start a file loading in the background.
284  */
285 static image *
286 alloc_image (ModeInfo *mi)
287 {
288   slideshow_state *ss = &sss[MI_SCREEN(mi)];
289   int wire = MI_IS_WIREFRAME(mi);
290   image *img = (image *) calloc (1, sizeof (*img));
291   Bool async_p = True;
292
293   img->id = ++ss->image_id;
294   img->loaded_p = False;
295   img->used_p = False;
296   img->mi = mi;
297
298   glGenTextures (1, &img->texid);
299   if (img->texid <= 0) abort();
300
301   ss->image_load_time = ss->now;
302
303   if (wire)
304     image_loaded_cb (0, 0, 0, 0, 0, 0, img);
305   else if (async_p)
306     screen_to_texture_async (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
307                              img->texid, image_loaded_cb, img);
308   else
309     {
310       char *filename = 0;
311       XRectangle geom;
312       int iw=0, ih=0, tw=0, th=0;
313       glBindTexture (GL_TEXTURE_2D, img->texid);
314
315       if (! screen_to_texture (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
316                                &filename, &geom, &iw, &ih, &tw, &th))
317         exit(1);
318       image_loaded_cb (filename, &geom, iw, ih, tw, th, img);
319       if (filename) free (filename);
320     }
321
322   ss->images[ss->nimages++] = img;
323   if (ss->nimages >= countof(ss->images)) abort();
324
325   return img;
326 }
327
328
329 /* Block until the first image is completely loaded.
330    We normally load images in the background, but we have nothing to draw
331    until we get that first image...
332  */
333 static void
334 await_first_image (ModeInfo *mi)
335 {
336   slideshow_state *ss = &sss[MI_SCREEN(mi)];
337   image *img;
338   int i = 0;
339   if (ss->nimages != 0) abort();
340   img = alloc_image (mi);
341
342   while (! img->loaded_p)
343     {
344       usleep (100000);         /* check every 1/10th sec */
345       if (i++ > 600) abort();  /* if a minute has passed, we're broken */
346
347       while (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
348         XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
349     }
350
351   if (debug_p)
352     fprintf (stderr, "\n");
353 }
354
355
356 /* Callback that tells us that the texture has been loaded.
357  */
358 static void
359 image_loaded_cb (const char *filename, XRectangle *geom,
360                  int image_width, int image_height,
361                  int texture_width, int texture_height,
362                  void *closure)
363 {
364   image *img = (image *) closure;
365   ModeInfo *mi = img->mi;
366   /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
367
368   int wire = MI_IS_WIREFRAME(mi);
369
370   if (wire)
371     {
372       img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
373       img->h = MI_HEIGHT (mi);
374       img->geom.width  = img->w;
375       img->geom.height = img->h;
376       goto DONE;
377     }
378
379   if (image_width == 0 || image_height == 0)
380     exit (1);
381
382   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
383   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
384                    mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
385
386   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
387   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
388
389   img->w  = image_width;
390   img->h  = image_height;
391   img->tw = texture_width;
392   img->th = texture_height;
393   img->geom = *geom;
394   img->title = (filename ? strdup (filename) : 0);
395
396   if (img->title)   /* strip filename to part after last /. */
397     {
398       char *s = strrchr (img->title, '/');
399       if (s) strcpy (img->title, s+1);
400     }
401
402   if (debug_p)
403     fprintf (stderr, "%s: loaded   img %2d: \"%s\"\n",
404              blurb(), img->id, (img->title ? img->title : "(null)"));
405  DONE:
406
407   img->loaded_p = True;
408 }
409
410
411
412 /* Free the image and texture, after nobody is referencing it.
413  */
414 static void
415 destroy_image (ModeInfo *mi, image *img)
416 {
417   slideshow_state *ss = &sss[MI_SCREEN(mi)];
418   Bool freed_p = False;
419   int i;
420
421   if (!img) abort();
422   if (!img->loaded_p) abort();
423   if (!img->used_p) abort();
424   if (img->texid <= 0) abort();
425   if (img->refcount != 0) abort();
426
427   for (i = 0; i < ss->nimages; i++)             /* unlink it from the list */
428     if (ss->images[i] == img)
429       {
430         int j;
431         for (j = i; j < ss->nimages-1; j++)     /* pull remainder forward */
432           ss->images[j] = ss->images[j+1];
433         ss->images[j] = 0;
434         ss->nimages--;
435         freed_p = True;
436         break;
437       }
438
439   if (!freed_p) abort();
440
441   if (debug_p)
442     fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
443              blurb(), img->id, (img->title ? img->title : "(null)"));
444
445   if (img->title) free (img->title);
446   glDeleteTextures (1, &img->texid);
447   free (img);
448 }
449
450
451 /* Return an image to use for a sprite.
452    If it's time for a new one, get a new one.
453    Otherwise, use an old one.
454    Might return 0 if the machine is really slow.
455  */
456 static image *
457 get_image (ModeInfo *mi)
458 {
459   slideshow_state *ss = &sss[MI_SCREEN(mi)];
460   image *img = 0;
461   double now = ss->now;
462   Bool want_new_p = (ss->change_now_p ||
463                      ss->image_load_time + image_seconds <= now);
464   image *new_img = 0;
465   image *old_img = 0;
466   image *loading_img = 0;
467   int i;
468
469   for (i = 0; i < ss->nimages; i++)
470     {
471       image *img2 = ss->images[i];
472       if (!img2) abort();
473       if (!img2->loaded_p)
474         loading_img = img2;
475       else if (!img2->used_p)
476         new_img = img2;
477       else
478         old_img = img2;
479     }
480
481   if (want_new_p && new_img)
482     img = new_img, new_img = 0, ss->change_now_p = False;
483   else if (old_img)
484     img = old_img, old_img = 0;
485   else if (new_img)
486     img = new_img, new_img = 0, ss->change_now_p = False;
487
488   /* Make sure that there is always one unused image in the pipe.
489    */
490   if (!new_img && !loading_img)
491     alloc_image (mi);
492
493   return img;
494 }
495
496
497 /* Pick random starting and ending positions for the given sprite.
498  */
499 static void
500 randomize_sprite (ModeInfo *mi, sprite *sp)
501 {
502   int vp_w = MI_WIDTH(mi);
503   int vp_h = MI_HEIGHT(mi);
504   int img_w = sp->img->geom.width;
505   int img_h = sp->img->geom.height;
506   int min_w, min_h, max_w, max_h;
507   double ratio = (double) img_h / img_w;
508
509   if (letterbox_p)
510     {
511       min_w = img_w;
512       min_h = img_h;
513     }
514   else
515     {
516       if (img_w < vp_w)
517         {
518           min_w = vp_w;
519           min_h = img_h * (float) vp_w / img_w;
520         }
521       else
522         {
523           min_w = img_w * (float) vp_h / img_h;
524           min_h = vp_h;
525         }
526     }
527
528   max_w = min_w * 100 / zoom;
529   max_h = min_h * 100 / zoom;
530
531   sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
532   sp->to.w   = max_w - frand ((max_w - min_w) * 0.4);
533   sp->from.h = sp->from.w * ratio;
534   sp->to.h   = sp->to.w   * ratio;
535
536   if (zoom == 100)      /* only one box, and it is centered */
537     {
538       sp->from.x = (sp->from.w > vp_w
539                     ? -(sp->from.w - vp_w) / 2
540                     :  (vp_w - sp->from.w) / 2);
541       sp->from.y = (sp->from.h > vp_h
542                     ? -(sp->from.h - vp_h) / 2
543                     :  (vp_h - sp->from.h) / 2);
544       sp->to = sp->from;
545     }
546   else                  /* position both boxes randomly */
547     {
548       sp->from.x = (sp->from.w > vp_w
549                     ? -frand (sp->from.w - vp_w)
550                     :  frand (vp_w - sp->from.w));
551       sp->from.y = (sp->from.h > vp_h
552                     ? -frand (sp->from.h - vp_h)
553                     :  frand (vp_h - sp->from.h));
554       sp->to.x   = (sp->to.w > vp_w
555                     ? -frand (sp->to.w - vp_w)
556                     :  frand (vp_w - sp->to.w));
557       sp->to.y   = (sp->to.h > vp_h
558                     ? -frand (sp->to.h - vp_h)
559                     :  frand (vp_h - sp->to.h));
560     }
561
562   if (random() & 1)
563     {
564       rect swap = sp->to;
565       sp->to = sp->from;
566       sp->from = swap;
567     }
568
569   /* Make sure the aspect ratios are within 0.0001 of each other.
570    */
571   if ((int) (0.5 + (sp->from.w * 1000 / sp->from.h)) !=
572       (int) (0.5 + (sp->to.w   * 1000 / sp->to.h)))
573     abort();
574
575   sp->from.x /= vp_w;
576   sp->from.y /= vp_h;
577   sp->from.w /= vp_w;
578   sp->from.h /= vp_h;
579   sp->to.x   /= vp_w;
580   sp->to.y   /= vp_h;
581   sp->to.w   /= vp_w;
582   sp->to.h   /= vp_h;
583 }
584
585
586 /* Allocate a new sprite and start its animation going.
587  */
588 static sprite *
589 new_sprite (ModeInfo *mi)
590 {
591   slideshow_state *ss = &sss[MI_SCREEN(mi)];
592   image *img = get_image (mi);
593   sprite *sp;
594
595   if (!img)
596     {
597       /* Oops, no images yet!  The machine is probably hurting bad.
598          Let's give it some time before thrashing again. */
599       usleep (250000);
600       return 0;
601     }
602
603   sp = (sprite *) calloc (1, sizeof (*sp));
604   sp->id = ++ss->sprite_id;
605   sp->start_time = ss->now;
606   sp->state_time = sp->start_time;
607   sp->state = sp->prev_state = NEW;
608   sp->img = img;
609
610   sp->img->refcount++;
611   sp->img->used_p = True;
612
613   ss->sprites[ss->nsprites++] = sp;
614   if (ss->nsprites >= countof(ss->sprites)) abort();
615
616   randomize_sprite (mi, sp);
617
618   return sp;
619 }
620
621
622 /* Free the given sprite, and decrement the reference count on its image.
623  */
624 static void
625 destroy_sprite (ModeInfo *mi, sprite *sp)
626 {
627   slideshow_state *ss = &sss[MI_SCREEN(mi)];
628   Bool freed_p = False;
629   image *img;
630   int i;
631
632   if (!sp) abort();
633   if (sp->state != DEAD) abort();
634   img = sp->img;
635   if (!img) abort();
636   if (!img->loaded_p) abort();
637   if (!img->used_p) abort();
638   if (img->refcount <= 0) abort();
639
640   for (i = 0; i < ss->nsprites; i++)            /* unlink it from the list */
641     if (ss->sprites[i] == sp)
642       {
643         int j;
644         for (j = i; j < ss->nsprites-1; j++)    /* pull remainder forward */
645           ss->sprites[j] = ss->sprites[j+1];
646         ss->sprites[j] = 0;
647         ss->nsprites--;
648         freed_p = True;
649         break;
650       }
651
652   if (!freed_p) abort();
653   free (sp);
654   sp = 0;
655
656   img->refcount--;
657   if (img->refcount < 0) abort();
658   if (img->refcount == 0)
659     destroy_image (mi, img);
660 }
661
662
663 /* Updates the sprite for the current frame of the animation based on
664    its creation time compared to the current wall clock.
665  */
666 static void
667 tick_sprite (ModeInfo *mi, sprite *sp)
668 {
669   slideshow_state *ss = &sss[MI_SCREEN(mi)];
670   image *img = sp->img;
671   double now = ss->now;
672   double secs;
673   double ratio;
674   rect prev_rect = sp->current;
675   GLfloat prev_opacity = sp->opacity;
676
677   if (! sp->img) abort();
678   if (! img->loaded_p) abort();
679
680   secs = now - sp->start_time;
681   ratio = secs / (pan_seconds + fade_seconds);
682   if (ratio > 1) ratio = 1;
683
684   sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
685   sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
686   sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
687   sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
688
689   sp->prev_state = sp->state;
690
691   if (secs < fade_seconds)
692     {
693       sp->state = IN;
694       sp->opacity = secs / (GLfloat) fade_seconds;
695     }
696   else if (secs < pan_seconds)
697     {
698       sp->state = FULL;
699       sp->opacity = 1;
700     }
701   else if (secs < pan_seconds + fade_seconds)
702     {
703       sp->state = OUT;
704       sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
705     }
706   else
707     {
708       sp->state = DEAD;
709       sp->opacity = 0;
710     }
711
712   if (sp->state != sp->prev_state &&
713       (sp->prev_state == IN ||
714        sp->prev_state == FULL))
715     {
716       double secs = now - sp->state_time;
717
718       if (debug_p)
719         fprintf (stderr,
720                  "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
721                  blurb(),
722                  (sp->prev_state == IN ? "fade" : "pan "),
723                  sp->frame_count,
724                  secs,
725                  sp->frame_count / secs,
726                  ss->theoretical_fps);
727
728       sp->state_time = now;
729       sp->frame_count = 0;
730     }
731
732   sp->frame_count++;
733
734   if (sp->state != DEAD &&
735       (prev_rect.x != sp->current.x ||
736        prev_rect.y != sp->current.y ||
737        prev_rect.w != sp->current.w ||
738        prev_rect.h != sp->current.h ||
739        prev_opacity != sp->opacity))
740     ss->redisplay_needed_p = True;
741 }
742
743
744 /* Draw the given sprite at the phase of its animation dictated by
745    its creation time compared to the current wall clock.
746  */
747 static void
748 draw_sprite (ModeInfo *mi, sprite *sp)
749 {
750   slideshow_state *ss = &sss[MI_SCREEN(mi)];
751   int wire = MI_IS_WIREFRAME(mi);
752   image *img = sp->img;
753
754   if (! sp->img) abort();
755   if (! img->loaded_p) abort();
756
757   glPushMatrix();
758   {
759     glTranslatef (sp->current.x, sp->current.y, 0);
760     glScalef (sp->current.w, sp->current.h, 1);
761
762     if (wire)                   /* Draw a grid inside the box */
763       {
764         GLfloat dy = 0.1;
765         GLfloat dx = dy * img->w / img->h;
766         GLfloat x, y;
767
768         if (sp->id & 1)
769           glColor4f (sp->opacity, 0, 0, 1);
770         else
771           glColor4f (0, 0, sp->opacity, 1);
772
773         glBegin(GL_LINES);
774         glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
775         glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
776
777         for (y = 0; y < 1+dy; y += dy)
778           {
779             GLfloat yy = (y > 1 ? 1 : y);
780             for (x = 0.5; x < 1+dx; x += dx)
781               {
782                 GLfloat xx = (x > 1 ? 1 : x);
783                 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
784                 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
785               }
786             for (x = 0.5; x > -dx; x -= dx)
787               {
788                 GLfloat xx = (x < 0 ? 0 : x);
789                 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
790                 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
791               }
792           }
793         glEnd();
794       }
795     else                        /* Draw the texture quad */
796       {
797         GLfloat texw  = img->geom.width  / (GLfloat) img->tw;
798         GLfloat texh  = img->geom.height / (GLfloat) img->th;
799         GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
800         GLfloat texy1 = img->geom.y / (GLfloat) img->th;
801         GLfloat texx2 = texx1 + texw;
802         GLfloat texy2 = texy1 + texh;
803
804         glBindTexture (GL_TEXTURE_2D, img->texid);
805         glColor4f (1, 1, 1, sp->opacity);
806         glNormal3f (0, 0, 1);
807         glBegin (GL_QUADS);
808         glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
809         glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
810         glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
811         glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
812         glEnd();
813
814         if (debug_p)            /* Draw a border around the image */
815           {
816             if (!wire) glDisable (GL_TEXTURE_2D);
817
818             if (sp->id & 1)
819               glColor4f (sp->opacity, 0, 0, 1);
820             else
821               glColor4f (0, 0, sp->opacity, 1);
822
823             glBegin (GL_LINE_LOOP);
824             glVertex3f (0, 0, 0);
825             glVertex3f (0, 1, 0);
826             glVertex3f (1, 1, 0);
827             glVertex3f (1, 0, 0);
828             glEnd();
829
830             if (!wire) glEnable (GL_TEXTURE_2D);
831           }
832       }
833
834
835     if (do_titles &&
836         img->title && *img->title)
837       {
838         int x = 10;
839         int y = mi->xgwa.height - 10;
840         glColor4f (0, 0, 0, sp->opacity);   /* cheap-assed dropshadow */
841         print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
842                          mi->xgwa.width, mi->xgwa.height, x, y,
843                          img->title);
844         x++; y++;
845         glColor4f (1, 1, 1, sp->opacity);
846         print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
847                          mi->xgwa.width, mi->xgwa.height, x, y,
848                          img->title);
849       }
850   }
851   glPopMatrix();
852
853   if (debug_p)
854     {
855       if (!wire) glDisable (GL_TEXTURE_2D);
856
857       if (sp->id & 1)
858         glColor4f (1, 0, 0, 1);
859       else
860         glColor4f (0, 0, 1, 1);
861
862       /* Draw the "from" and "to" boxes
863        */
864       glBegin (GL_LINE_LOOP);
865       glVertex3f (sp->from.x,              sp->from.y,              0);
866       glVertex3f (sp->from.x + sp->from.w, sp->from.y,              0);
867       glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
868       glVertex3f (sp->from.x,              sp->from.y + sp->from.h, 0);
869       glEnd();
870
871       glBegin (GL_LINE_LOOP);
872       glVertex3f (sp->to.x,                sp->to.y,                0);
873       glVertex3f (sp->to.x + sp->to.w,     sp->to.y,                0);
874       glVertex3f (sp->to.x + sp->to.w,     sp->to.y + sp->to.h,     0);
875       glVertex3f (sp->to.x,                sp->to.y + sp->to.h,     0);
876       glEnd();
877
878       if (!wire) glEnable (GL_TEXTURE_2D);
879     }
880 }
881
882
883 static void
884 tick_sprites (ModeInfo *mi)
885 {
886   slideshow_state *ss = &sss[MI_SCREEN(mi)];
887   int i;
888   for (i = 0; i < ss->nsprites; i++)
889       tick_sprite (mi, ss->sprites[i]);
890 }
891
892
893 static void
894 draw_sprites (ModeInfo *mi)
895 {
896   slideshow_state *ss = &sss[MI_SCREEN(mi)];
897   int i;
898
899   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
900
901   glPushMatrix();
902   for (i = 0; i < ss->nsprites; i++)
903     draw_sprite (mi, ss->sprites[i]);
904   glPopMatrix();
905
906   if (debug_p)                          /* draw a white box (the "screen") */
907     {
908       int wire = MI_IS_WIREFRAME(mi);
909
910       if (!wire) glDisable (GL_TEXTURE_2D);
911
912       glColor4f (1, 1, 1, 1);
913       glBegin (GL_LINE_LOOP);
914       glVertex3f (0, 0, 0);
915       glVertex3f (0, 1, 0);
916       glVertex3f (1, 1, 0);
917       glVertex3f (1, 0, 0);
918       glEnd();
919
920       if (!wire) glEnable (GL_TEXTURE_2D);
921     }
922 }
923
924
925 void
926 reshape_slideshow (ModeInfo *mi, int width, int height)
927 {
928   slideshow_state *ss = &sss[MI_SCREEN(mi)];
929   GLfloat s;
930   glViewport (0, 0, width, height);
931   glMatrixMode (GL_PROJECTION);
932   glLoadIdentity();
933   glMatrixMode (GL_MODELVIEW);
934   glLoadIdentity();
935
936   s = 2;
937
938   if (debug_p)
939     {
940       s *= (zoom / 100.0) * 0.75;
941       if (s < 0.1) s = 0.1;
942     }
943
944   glScalef (s, s, s);
945   glTranslatef (-0.5, -0.5, 0);
946
947   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
948
949   ss->redisplay_needed_p = True;
950 }
951
952
953 Bool
954 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
955 {
956   slideshow_state *ss = &sss[MI_SCREEN(mi)];
957
958   if (event->xany.type == ButtonPress &&
959       event->xbutton.button == Button1)
960     {
961       ss->change_now_p = True;
962       return True;
963     }
964   else if (event->xany.type == KeyPress)
965     {
966       KeySym keysym;
967       char c = 0;
968       XLookupString (&event->xkey, &c, 1, &keysym, 0);
969       if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
970         {
971           ss->change_now_p = True;
972           return True;
973         }
974     }
975   else if (event->xany.type == Expose ||
976            event->xany.type == GraphicsExpose ||
977            event->xany.type == VisibilityNotify)
978     {
979       ss->redisplay_needed_p = True;
980       if (debug_p)
981         fprintf (stderr, "%s: exposure\n", blurb());
982       return False;
983     }
984
985   return False;
986 }
987
988
989 /* Do some sanity checking on various user-supplied values, and make
990    sure they are all internally consistent.
991  */
992 static void
993 sanity_check (ModeInfo *mi)
994 {
995   if (zoom < 1) zoom = 1;           /* zoom is a positive percentage */
996   else if (zoom > 100) zoom = 100;
997
998   if (zoom == 100)                  /* with no zooming, there is no panning */
999     pan_seconds = 0;
1000
1001   if (pan_seconds < fade_seconds)   /* pan is inclusive of fade */
1002     pan_seconds = fade_seconds;
1003
1004   if (pan_seconds == 0)             /* no zero-length cycles, please... */
1005     pan_seconds = 1;
1006
1007   if (image_seconds < pan_seconds)  /* we only change images at fade-time */
1008     image_seconds = pan_seconds;
1009
1010   /* If we're not panning/zooming within the image, then there's no point
1011      in crossfading the image with itself -- only do crossfades when changing
1012      to a new image. */
1013   if (zoom == 100 && pan_seconds < image_seconds)
1014     pan_seconds = image_seconds;
1015
1016   /* No need to use mipmaps if we're not changing the image size much */
1017   if (zoom >= 80) mipmap_p = False;
1018
1019   if      (fps_cutoff < 0)  fps_cutoff = 0;
1020   else if (fps_cutoff > 30) fps_cutoff = 30;
1021 }
1022
1023
1024 static void
1025 check_fps (ModeInfo *mi)
1026 {
1027   slideshow_state *ss = &sss[MI_SCREEN(mi)];
1028
1029   static double time_elapsed = 0;
1030   static int frames_elapsed = 0;
1031   double start_time, end_time, wall_elapsed, frame_duration, fps;
1032   int i;
1033
1034   start_time = ss->now;
1035   end_time = double_time();
1036   frame_duration = end_time - start_time;   /* time spent drawing this frame */
1037   time_elapsed += frame_duration;           /* time spent drawing all frames */
1038   frames_elapsed++;
1039
1040   wall_elapsed = end_time - ss->dawn_of_time;
1041   fps = frames_elapsed / time_elapsed;
1042   ss->theoretical_fps = fps;
1043
1044   if (ss->checked_fps_p) return;
1045
1046   if (wall_elapsed <= 8)    /* too early to be sure */
1047     return;
1048
1049   ss->checked_fps_p = True;
1050
1051   if (fps >= fps_cutoff)
1052     {
1053       if (debug_p)
1054         fprintf (stderr,
1055                  "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1056                  blurb(), fps, frames_elapsed, wall_elapsed);
1057       return;
1058     }
1059
1060   fprintf (stderr,
1061            "%s: only %.1f fps!  Turning off pan/fade to compensate...\n",
1062            blurb(), fps);
1063   zoom = 100;
1064   fade_seconds = 0;
1065
1066   sanity_check (mi);
1067
1068   for (i = 0; i < ss->nsprites; i++)
1069     {
1070       sprite *sp = ss->sprites[i];
1071       randomize_sprite (mi, sp);
1072       sp->state = FULL;
1073     }
1074
1075   ss->redisplay_needed_p = True;
1076
1077   /* Need this in case zoom changed. */
1078   reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1079 }
1080
1081
1082 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1083  */
1084 static void
1085 hack_resources (void)
1086 {
1087 #if 0
1088   char *res = "desktopGrabber";
1089   char *val = get_string_resource (res, "DesktopGrabber");
1090   char buf1[255];
1091   char buf2[255];
1092   XrmValue value;
1093   sprintf (buf1, "%.100s.%.100s", progclass, res);
1094   sprintf (buf2, "%.200s -v", val);
1095   value.addr = buf2;
1096   value.size = strlen(buf2);
1097   XrmPutResource (&db, buf1, "String", &value);
1098 #endif
1099 }
1100
1101
1102 void
1103 init_slideshow (ModeInfo *mi)
1104 {
1105   int screen = MI_SCREEN(mi);
1106   slideshow_state *ss;
1107   int wire = MI_IS_WIREFRAME(mi);
1108   
1109   if (sss == NULL) {
1110     if ((sss = (slideshow_state *)
1111          calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1112       return;
1113   }
1114   ss = &sss[screen];
1115
1116   if ((ss->glx_context = init_GL(mi)) != NULL) {
1117     reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1118   } else {
1119     MI_CLEARWINDOW(mi);
1120   }
1121
1122   if (debug_p)
1123     fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1124              blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1125
1126   sanity_check(mi);
1127
1128   if (debug_p)
1129     fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1130              blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1131
1132   glDisable (GL_LIGHTING);
1133   glDisable (GL_DEPTH_TEST);
1134   glDepthMask (GL_FALSE);
1135   glEnable (GL_CULL_FACE);
1136   glCullFace (GL_BACK);
1137
1138   if (! wire)
1139     {
1140       glEnable (GL_TEXTURE_2D);
1141       glShadeModel (GL_SMOOTH);
1142       glEnable (GL_BLEND);
1143       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1144     }
1145
1146   if (debug_p) glLineWidth (3);
1147
1148   load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1149
1150   if (debug_p)
1151     hack_resources();
1152
1153   ss->now = double_time();
1154   ss->dawn_of_time = ss->now;
1155   ss->prev_frame_time = ss->now;
1156
1157   await_first_image (mi);   /* wait for first image to fully load */
1158
1159   ss->now = double_time();
1160   ss->dawn_of_time = ss->now;
1161
1162   new_sprite (mi);          /* start first sprite fading in */
1163 }
1164
1165
1166 void
1167 draw_slideshow (ModeInfo *mi)
1168 {
1169   slideshow_state *ss = &sss[MI_SCREEN(mi)];
1170   int i;
1171
1172   if (!ss->glx_context)
1173     return;
1174
1175   ss->now = double_time();
1176
1177   /* Each sprite has three states: fading in, full, fading out.
1178      The in/out states overlap like this:
1179
1180      iiiiiiFFFFFFFFFFFFoooooo  . . . . . . . . . . . . . . . . . 
1181      . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo  . . . . . . . .
1182      . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1183
1184      So as soon as a sprite goes into the "out" state, we create
1185      a new sprite (in the "in" state.)
1186    */
1187
1188   if (ss->nsprites > 2) abort();
1189
1190   /* If a sprite is just entering the fade-out state,
1191      then add a new sprite in the fade-in state.
1192    */
1193   for (i = 0; i < ss->nsprites; i++)
1194     {
1195       sprite *sp = ss->sprites[i];
1196       if (sp->state != sp->prev_state &&
1197           sp->state == (fade_seconds == 0 ? DEAD : OUT))
1198         new_sprite (mi);
1199     }
1200
1201   tick_sprites (mi);
1202
1203   /* Now garbage collect the dead sprites.
1204    */
1205   for (i = 0; i < ss->nsprites; i++)
1206     {
1207       sprite *sp = ss->sprites[i];
1208       if (sp->state == DEAD)
1209         {
1210           destroy_sprite (mi, sp);
1211           i--;
1212         }
1213     }
1214
1215   /* We can only ever end up with no sprites at all if the machine is
1216      being really slow and we hopped states directly from FULL to DEAD
1217      without passing OUT... */
1218   if (ss->nsprites == 0)
1219     new_sprite (mi);
1220
1221   if (!ss->redisplay_needed_p)
1222     return;
1223
1224   if (debug_p && ss->now - ss->prev_frame_time > 1)
1225     fprintf (stderr, "%s: static screen for %.1f secs\n",
1226              blurb(), ss->now - ss->prev_frame_time);
1227
1228   draw_sprites (mi);
1229
1230   ss->fps = fps_1 (mi);
1231   if (mi->fps_p) fps_2 (mi);
1232
1233   glFinish();
1234   glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1235   ss->prev_frame_time = ss->now;
1236   ss->redisplay_needed_p = False;
1237   check_fps (mi);
1238 }
1239
1240 #endif /* USE_GL */