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