ftp://ftp.linux.ncsu.edu/mirror/ftp.redhat.com/pub/redhat/linux/enterprise/4/en/os...
[xscreensaver] / hacks / glx / glslideshow.c
1 /* glslideshow, Copyright (c) 2003, 2004 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  * TODO:
19  *
20  * - Resizing the window makes everything go black forevermore.  No idea why.
21  *
22  *
23  * - When a new image is loaded, there is a glitch: animation pauses during
24  *   the period when we're loading the image-to-fade-in.  On fast (2GHz)
25  *   machines, this stutter is short but noticable (usually less than half a
26  *   second.)  On slower machines, it can be much more pronounced.
27  *
28  *   In xscreensaver 4.17, I added the new functions fork_load_random_image()
29  *   and fork_screen_to_ximage() to make it possible to do image loading in
30  *   the background, in an attempt to solve this (the idea being to only swap
31  *   in the new image once it has been loaded.)  Using those routines, we
32  *   continue animating while the file system is being searched for an image
33  *   file; while that image data is read, parsed, and decompressed; while that
34  *   data is placed on a Pixmap in the X server.
35  *
36  *   However, two things still happen in the "parent" (glslideshow) process:
37  *   converting that server-side Pixmap to a client-side XImage (XGetImage);
38  *   and converting that XImage to an OpenGL texture (gluBuild2DMipmaps).
39  *   It's possible that some new code would allow us to do the Pixmap-to-XImage
40  *   conversion in the forked process (feed it back upstream through a pipe or
41  *   SHM segment or something); however, it turns out that significant
42  *   parent-process image-loading time is being spent in gluBuild2DMipmaps().
43  *
44  *   So, the next step would be to figure out some way to create a texture on
45  *   the other end of the fork that would be usable by the parent process.  Is
46  *   that even possible?  Is it possible to use a single GLX context in a
47  *   multithreaded way like that?  (Or use a second GLX context, but allow the
48  *   two contexts to share data?)
49  *
50  *   Another question remains: is the stalling happening in the GL/GLX
51  *   libraries, or are we actually seeing a stall on the graphics pipeline?
52  *   If the latter, then no amount of threading would help, because the
53  *   bottleneck is pushing the bits from system memory to the graphics card.
54  *
55  *   How does Apple do this with their MacOSX slideshow screen saver?  Perhaps
56  *   it's easier for them because their OpenGL libraries have thread support
57  *   at a lower level?
58  *
59  *
60  * - Even if the glitch was solved, there's still a bug in the background
61  *   loading of images: as soon as the image comes in, we slap it into place
62  *   in the target quad.  This can lead to an image being changed while it is
63  *   still being drawn, if that quad happens to be visible already.  Instead,
64  *   when the callback goes off, we should make sure to load it into the
65  *   invisible quad, or if both are visible, we should wait until one goes
66  *   invisible and then load it there (in other words, wait for the next
67  *   fade-out to end.)
68  */
69
70 #include <X11/Intrinsic.h>
71
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_DEBUG          "False"
88
89 #define DEFAULTS  "*delay:           20000                \n" \
90                   "*fadeDuration:  " DEF_FADE_DURATION   "\n" \
91                   "*panDuration:   " DEF_PAN_DURATION    "\n" \
92                   "*imageDuration: " DEF_IMAGE_DURATION  "\n" \
93                   "*zoom:          " DEF_ZOOM            "\n" \
94                   "*FPScutoff:     " DEF_FPS_CUTOFF      "\n" \
95                   "*debug   :      " DEF_DEBUG           "\n" \
96                   "*wireframe:       False                \n" \
97                   "*showFPS:         False                \n" \
98                   "*fpsSolid:        True                 \n" \
99                   "*titles:        " DEF_TITLES  "\n" \
100                   "*titleFont:       -*-times-bold-r-normal-*-180-*\n" \
101                   "*desktopGrabber:  xscreensaver-getimage -no-desktop %s\n"
102
103 # include "xlockmore.h"
104
105 #undef countof
106 #define countof(x) (sizeof((x))/sizeof((*x)))
107
108 #ifdef USE_GL
109
110 #include <GL/glu.h>
111 #include <math.h>
112 #include <sys/time.h>
113 #include <stdio.h>
114 #include <stdlib.h>
115 #include "grab-ximage.h"
116
117 typedef struct {
118   GLfloat x, y, w, h;
119 } rect;
120
121 typedef struct {
122   GLuint texid;                    /* which texture to draw */
123   enum { IN, OUT, DEAD } state;    /* how to draw it */
124   rect from, to;                   /* the journey this quad is taking */
125   char *title;
126 } gls_quad;
127
128
129 typedef struct {
130   GLXContext *glx_context;
131   time_t start_time;            /* when we started displaying this image */
132
133   int motion_frames;            /* how many frames each pan takes */
134   int fade_frames;              /* how many frames fading in/out takes */
135
136   gls_quad quads[2];            /* the (up to) 2 quads we animate */
137   GLuint texids[2];             /* textures: "old" and "new" */
138   GLuint current_texid;         /* the "new" one */
139
140   int img_w, img_h;             /* Size (pixels) of currently-loaded image */
141
142   double now;                   /* current time in seconds */
143   double pan_start_time;        /* when this pan began */
144   double image_start_time;      /* when this image was loaded */
145   double dawn_of_time;          /* when the program launched */
146
147   Bool redisplay_needed_p;      /* Sometimes we can get away with not
148                                    re-painting.  Tick this if a redisplay
149                                    is required. */
150
151   GLfloat fps;                  /* approximate frame rate we're achieving */
152   int pan_frame_count;          /* More frame-rate stats */
153   int fade_frame_count;
154   Bool low_fps_p;               /* Whether we have compensated for a low
155                                    frame rate. */
156
157   Bool fork_p;                  /* threaded image loading; #### still buggy */
158
159   XFontStruct *xfont;
160   GLuint font_dlist;
161
162 } slideshow_state;
163
164 static slideshow_state *sss = NULL;
165
166
167 /* Command-line arguments
168  */
169 static int fade_seconds;    /* Duration in seconds of fade transitions.
170                                If 0, jump-cut instead of fading. */
171 static int pan_seconds;     /* Duration of each pan through an image. */
172 static int image_seconds;   /* How many seconds until loading a new image. */
173 static int zoom;            /* How far in to zoom when panning, in percent of
174                                image size: that is, 75 means "when zoomed all
175                                the way in, 75% of the image will be visible."
176                              */
177 static int fps_cutoff;      /* If the frame-rate falls below this, turn off
178                                zooming.*/
179 static Bool do_titles;      /* Display image titles. */
180 static Bool debug_p;        /* Be loud and do weird things. */
181
182
183 static XrmOptionDescRec opts[] = {
184   {"-fade",     ".slideshow.fadeDuration",  XrmoptionSepArg, 0     },
185   {"-pan",      ".slideshow.panDuration",   XrmoptionSepArg, 0     },
186   {"-duration", ".slideshow.imageDuration", XrmoptionSepArg, 0     },
187   {"-zoom",     ".slideshow.zoom",          XrmoptionSepArg, 0     },
188   {"-cutoff",   ".slideshow.FPScutoff",     XrmoptionSepArg, 0     },
189   {"-titles",   ".slideshow.titles",        XrmoptionNoArg, "True" },
190   {"+titles",   ".slideshow.titles",        XrmoptionNoArg, "True" },
191   {"-debug",    ".slideshow.debug",         XrmoptionNoArg, "True" },
192 };
193
194 static argtype vars[] = {
195   { &fade_seconds,  "fadeDuration", "FadeDuration", DEF_FADE_DURATION,  t_Int},
196   { &pan_seconds,   "panDuration",  "PanDuration",  DEF_PAN_DURATION,   t_Int},
197   { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
198   { &zoom,          "zoom",         "Zoom",         DEF_ZOOM,           t_Int},
199   { &fps_cutoff,    "FPScutoff",    "FPSCutoff",    DEF_FPS_CUTOFF,     t_Int},
200   { &debug_p,       "debug",        "Debug",        DEF_DEBUG,         t_Bool},
201   { &do_titles,     "titles",       "Titles",       DEF_TITLES,        t_Bool},
202 };
203
204 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
205
206
207 static const char *
208 blurb (void)
209 {
210   static char buf[255];
211   time_t now = time ((time_t *) 0);
212   char *ct = (char *) ctime (&now);
213   int n = strlen(progname);
214   if (n > 100) n = 99;
215   strncpy(buf, progname, n);
216   buf[n++] = ':';
217   buf[n++] = ' ';
218   strncpy(buf+n, ct+11, 8);
219   strcpy(buf+n+9, ": ");
220   return buf;
221 }
222
223
224 /* Returns the current time in seconds as a double.
225  */
226 static double
227 double_time (void)
228 {
229   struct timeval now;
230 # ifdef GETTIMEOFDAY_TWO_ARGS
231   struct timezone tzp;
232   gettimeofday(&now, &tzp);
233 # else
234   gettimeofday(&now);
235 # endif
236
237   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
238 }
239
240
241 static void
242 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
243 {
244   const char *font = get_string_resource (res, "Font");
245   XFontStruct *f;
246   Font id;
247   int first, last;
248
249   if (!font) font = "-*-times-bold-r-normal-*-180-*";
250
251   f = XLoadQueryFont(mi->dpy, font);
252   if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
253
254   id = f->fid;
255   first = f->min_char_or_byte2;
256   last = f->max_char_or_byte2;
257   
258   clear_gl_error ();
259   *dlistP = glGenLists ((GLuint) last+1);
260   check_gl_error ("glGenLists");
261   glXUseXFont(id, first, last-first+1, *dlistP + first);
262   check_gl_error ("glXUseXFont");
263
264   *fontP = f;
265 }
266
267
268 static void
269 print_title_string (ModeInfo *mi, const char *string, GLfloat x, GLfloat y)
270 {
271   slideshow_state *ss = &sss[MI_SCREEN(mi)];
272   XFontStruct *font = ss->xfont;
273   GLfloat line_height = font->ascent + font->descent;
274
275   y -= line_height;
276
277   glPushAttrib (GL_TRANSFORM_BIT |  /* for matrix contents */
278                 GL_ENABLE_BIT);     /* for various glDisable calls */
279   glDisable (GL_LIGHTING);
280   glDisable (GL_DEPTH_TEST);
281   {
282     glMatrixMode(GL_PROJECTION);
283     glPushMatrix();
284     {
285       glLoadIdentity();
286
287       glMatrixMode(GL_MODELVIEW);
288       glPushMatrix();
289       {
290         unsigned int i;
291         int x2 = x;
292         glLoadIdentity();
293
294         gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
295
296         glRasterPos2f (x, y);
297         for (i = 0; i < strlen(string); i++)
298           {
299             char c = string[i];
300             if (c == '\n')
301               {
302                 glRasterPos2f (x, (y -= line_height));
303                 x2 = x;
304               }
305             else
306               {
307                 glCallList (ss->font_dlist + (int)(c));
308                 x2 += (font->per_char
309                        ? font->per_char[c - font->min_char_or_byte2].width
310                        : font->min_bounds.width);
311               }
312           }
313       }
314       glPopMatrix();
315     }
316     glMatrixMode(GL_PROJECTION);
317     glPopMatrix();
318   }
319   glPopAttrib();
320
321   glMatrixMode(GL_MODELVIEW);
322 }
323
324
325 static void
326 draw_quad (ModeInfo *mi, gls_quad *q)
327 {
328   slideshow_state *ss = &sss[MI_SCREEN(mi)];
329   int wire = MI_IS_WIREFRAME(mi);
330   GLfloat ratio;
331   rect current;
332   GLfloat opacity;
333   double secs;
334   GLfloat texw = 0;
335   GLfloat texh = 0;
336
337   if (q->state == DEAD)
338     return;
339
340   secs = ss->now - ss->pan_start_time;
341
342   if (q->state == OUT)
343     secs += pan_seconds;
344
345   ratio = secs / (pan_seconds + fade_seconds);
346
347   current.x = q->from.x + ratio * (q->to.x - q->from.x);
348   current.y = q->from.y + ratio * (q->to.y - q->from.y);
349   current.w = q->from.w + ratio * (q->to.w - q->from.w);
350   current.h = q->from.h + ratio * (q->to.h - q->from.h);
351
352   if (secs < fade_seconds)
353     opacity = secs / (GLfloat) fade_seconds;    /* fading in or out... */
354   else if (secs < pan_seconds)
355     opacity = 1;                                /* panning opaquely. */
356   else
357     opacity = 1 - ((secs - pan_seconds) /
358                    (GLfloat) fade_seconds);    /* fading in or out... */
359
360   if (q->state == OUT && opacity < 0.0001)
361     q->state = DEAD;
362
363   glPushMatrix();
364
365   glTranslatef (current.x, current.y, 0);
366   glScalef (current.w, current.h, 1);
367
368   if (!wire)
369     {
370       texw = mi->xgwa.width  / (GLfloat) ss->img_w;
371       texh = mi->xgwa.height / (GLfloat) ss->img_h;
372
373       glEnable (GL_TEXTURE_2D);
374       glEnable (GL_BLEND);
375       glBindTexture (GL_TEXTURE_2D, q->texid);
376       glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
377       glDepthMask (GL_FALSE);
378
379       /* Draw the texture quad
380        */
381       glColor4f (1, 1, 1, opacity);
382       glNormal3f (0, 0, 1);
383       glBegin (GL_QUADS);
384       glTexCoord2f (0,    0);    glVertex3f (0, 0, 0);
385       glTexCoord2f (0,    texh); glVertex3f (0, 1, 0);
386       glTexCoord2f (texw, texh); glVertex3f (1, 1, 0);
387       glTexCoord2f (texw, 0);    glVertex3f (1, 0, 0);
388       glEnd();
389
390       glDisable (GL_TEXTURE_2D);
391       glDisable (GL_BLEND);
392     }
393
394   if (wire)
395     glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
396                (q->texid == ss->texids[0] ? 0 : opacity),
397                opacity);
398   else
399     glColor4f (1, 1, 1, opacity);
400
401
402   /* Draw a grid inside the box
403    */
404   if (wire)
405     {
406       GLfloat d = 0.1;
407       GLfloat x, y;
408       glBegin(GL_LINES);
409       glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
410       glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
411
412       for (y = 0; y < 1+d; y += d)
413         for (x = 0; x < 1+d; x += d)
414           {
415             glVertex3f (0, y, 0); glVertex3f (1, y, 0);
416             glVertex3f (x, 0, 0); glVertex3f (x, 1, 0);
417           }
418       glEnd();
419     }
420
421   if (do_titles &&
422       q->state != DEAD &&
423       q->title && *q->title)
424     {
425       /* #### this is wrong -- I really want to draw this with
426          "1,1,1,opacity", so that the text gets laid down on top
427          of the image with alpha, but that doesn't work, and I
428          don't know why...
429        */
430       glColor4f (opacity, opacity, opacity, 1);
431       print_title_string (mi, q->title,
432                           10, mi->xgwa.height - 10);
433     }
434
435   glPopMatrix();
436
437   if (debug_p)
438     {
439       /* Draw the "from" and "to" boxes
440        */
441       glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
442                  (q->texid == ss->texids[0] ? 0 : opacity),
443                  opacity);
444
445       glBegin (GL_LINE_LOOP);
446       glVertex3f (q->from.x,             q->from.y,             0);
447       glVertex3f (q->from.x + q->from.w, q->from.y,             0);
448       glVertex3f (q->from.x + q->from.w, q->from.y + q->from.h, 0);
449       glVertex3f (q->from.x,             q->from.y + q->from.h, 0);
450       glEnd();
451
452       glBegin (GL_LINE_LOOP);
453       glVertex3f (q->to.x,               q->to.y,               0);
454       glVertex3f (q->to.x + q->to.w,     q->to.y,               0);
455       glVertex3f (q->to.x + q->to.w,     q->to.y + q->to.h,     0);
456       glVertex3f (q->to.x,               q->to.y + q->to.h,     0);
457       glEnd();
458     }
459 }
460
461
462 static void
463 draw_quads (ModeInfo *mi)
464 {
465   slideshow_state *ss = &sss[MI_SCREEN(mi)];
466   GLfloat s, o;
467   int i;
468
469   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
470
471   glPushMatrix();
472
473   s = (100.0 / zoom);
474   o = (1-s)/2;
475   glTranslatef (o, o, 0);
476   glScalef (s, s, s);
477
478   for (i = 0; i < countof(ss->quads); i++)
479     draw_quad (mi, &ss->quads[i]);
480
481   glPopMatrix();
482
483   if (debug_p)
484     {
485       glColor4f (1, 1, 1, 1);
486       glBegin (GL_LINE_LOOP);
487       glVertex3f (0, 0, 0);
488       glVertex3f (0, 1, 0);
489       glVertex3f (1, 1, 0);
490       glVertex3f (1, 0, 0);
491       glEnd();
492     }
493 }
494
495
496 /* Re-randomize the state of the given quad.
497  */
498 static void
499 reset_quad (ModeInfo *mi, gls_quad *q)
500 {
501 /*  slideshow_state *ss = &sss[MI_SCREEN(mi)];*/
502
503   GLfloat mid_w = (zoom / 100.0);
504   GLfloat mid_h = (zoom / 100.0);
505   GLfloat mid_x = (1 - mid_w) / 2;
506   GLfloat mid_y = (1 - mid_h) / 2;
507
508   GLfloat small = mid_w + frand ((1 - mid_w) * 0.3);
509 #if 0
510   GLfloat large = small + frand ((1 - small) / 2) + ((1 - small) / 2);
511 #else
512   GLfloat large = small + frand (1 - small);
513 #endif
514
515   if (q->state != DEAD)
516     abort();    /* we should only be resetting a quad when it's not visible. */
517
518   /* Possible box sizes range between "zoom" and "100%".
519      Pick a small box size, and a large box size.
520      Assign each a random position within the 1x1 box,
521      such that they encompass the middle "zoom" percentage.
522      One of those is the start, and one is the end.
523      Each frame will transition between one and the other.
524    */
525
526   if (random() & 1)
527     {
528       q->from.w = small; q->from.h = small;
529       q->to.w   = large; q->to.h   = large;
530     }
531   else
532     {
533       q->from.w = large; q->from.h = large;
534       q->to.w   = small; q->to.h   = small;
535     }
536
537   q->from.x = mid_x - frand (q->from.w - mid_w);
538   q->from.y = mid_y - frand (q->from.h - mid_h);
539   q->to.x   = mid_x - frand (q->to.w - mid_w);
540   q->to.y   = mid_y - frand (q->to.w - mid_h);
541
542   q->state = IN;
543 }
544
545
546 /* Shrinks the XImage by a factor of two.
547  */
548 static void
549 shrink_image (ModeInfo *mi, XImage *ximage)
550 {
551   int w2 = ximage->width/2;
552   int h2 = ximage->height/2;
553   int x, y;
554   XImage *ximage2;
555
556   if (w2 <= 32 || h2 <= 32)   /* let's not go crazy here, man. */
557     return;
558
559   if (debug_p)
560     fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
561              blurb(), ximage->width, ximage->height, w2, h2);
562
563   ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
564                           32, ZPixmap, 0, 0,
565                           w2, h2, 32, 0);
566   ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
567   if (!ximage2->data)
568     {
569       fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
570                blurb(), ximage->width, ximage->height, w2, h2);
571       exit (1);
572     }
573   for (y = 0; y < h2; y++)
574     for (x = 0; x < w2; x++)
575       XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
576   free (ximage->data);
577   *ximage = *ximage2;
578   ximage2->data = 0;
579   XFree (ximage2);
580 }
581
582
583 /* Load a new image into a texture for the given quad.
584  */
585 static void
586 load_quad_1 (ModeInfo *mi, gls_quad *q, XImage *ximage,
587              const char *filename, double start_time, double cvt_time)
588 {
589   slideshow_state *ss = &sss[MI_SCREEN(mi)];
590   int status;
591   int max_reduction = 7;
592   int err_count = 0;
593   int wire = MI_IS_WIREFRAME(mi);
594   double load_time=0, mipmap_time=0;   /* for debugging messages */
595
596   /* if (q->state != DEAD) abort(); */
597
598   /* Figure out which texid is currently in use, and pick the other one.
599    */
600   {
601     GLuint tid = 0;
602     int i;
603     if (ss->current_texid == 0)
604       tid = ss->texids[0];
605     else
606       for (i = 0; i < countof(ss->texids); i++)
607         if (ss->texids[i] != ss->current_texid)
608           {
609             tid = ss->texids[i];
610             break;
611           }
612
613     if (tid == 0) abort();   /* both textures in use by visible quads? */
614     q->texid = tid;
615     ss->current_texid = tid;
616   }
617
618   if (wire)
619     goto DONE;
620
621   if (q->title) free (q->title);
622   q->title = (filename ? strdup (filename) : 0);
623
624   if (q->title)   /* strip filename to part after last /. */
625     {
626       char *s = strrchr (q->title, '/');
627       if (s) strcpy (q->title, s+1);
628     }
629
630   if (debug_p)
631     {
632       fprintf (stderr, "%s: debug: loaded    image %d: \"%s\"\n",
633                blurb(), q->texid, (q->title ? q->title : "(null)"));
634       load_time = double_time();
635     }
636
637   glBindTexture (GL_TEXTURE_2D, q->texid);
638   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
639   glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
640                    GL_LINEAR_MIPMAP_LINEAR);
641   
642   ss->img_w = ximage->width;
643   ss->img_h = ximage->height;
644
645  AGAIN:
646
647   clear_gl_error();
648   status = gluBuild2DMipmaps (GL_TEXTURE_2D, 3,
649                               ximage->width, ximage->height,
650                               GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
651   
652   if(!status && glGetError())
653    /* Some implementations of gluBuild2DMipmaps(), but set a GL error anyway.
654       We could just call check_gl_error(), but that would exit. */
655     status = -1;
656
657   if (status)
658     {
659       char buf[100];
660       const char *s = (char *) gluErrorString (status);
661
662       if (!s || !*s)
663         {
664           sprintf (buf, "unknown error %d", status);
665           s = buf;
666         }
667
668       clear_gl_error();
669
670       if (++err_count > max_reduction)
671         {
672           fprintf(stderr,
673                   "\n"
674                   "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
675                   "%s: GLU said: \"%s\".\n"
676                   "%s: probably this means "
677                   "\"your video card is worthless and weak\"?\n\n",
678                   blurb(), MI_WIDTH(mi), MI_HEIGHT(mi),
679                   ximage->width, ximage->height,
680                   blurb(), s,
681                   blurb());
682           exit (1);
683         }
684       else
685         {
686           if (debug_p)
687             fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
688                      blurb(), ximage->width, ximage->height, s);
689           shrink_image (mi, ximage);
690           goto AGAIN;
691         }
692     }
693
694   check_gl_error("mipmapping");  /* should get a return code instead of a
695                                     GL error, but just in case... */
696
697   free(ximage->data);
698   ximage->data = 0;
699   XDestroyImage(ximage);
700
701   if (debug_p)
702     {
703       fprintf (stderr, "%s: debug: mipmapped image %d: %dx%d\n",
704                blurb(), q->texid, mi->xgwa.width, mi->xgwa.height);
705       mipmap_time = double_time();
706     }
707
708   if (cvt_time == 0)
709     cvt_time = load_time;
710   if (debug_p)
711     fprintf (stderr,
712              "%s: debug: load time elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
713              blurb(),
714              cvt_time    - start_time,
715              load_time   - cvt_time,
716              mipmap_time - load_time,
717              mipmap_time - start_time);
718
719  DONE:
720
721   /* Re-set "now" so that time spent loading the image file does not count
722      against the time remaining in this stage of the animation: image loading,
723      if it takes a perceptible amount of time, will cause the animation to
724      pause, but will not cause it to drop frames.
725    */
726   ss->now = double_time ();
727   ss->image_start_time = ss->now;
728
729   ss->redisplay_needed_p = True;
730 }
731
732
733 static void slideshow_load_cb (Screen *, Window, XImage *,
734                                const char *filename, void *closure,
735                                double cvt_time);
736
737 typedef struct {
738   ModeInfo *mi;
739   gls_quad *q;
740   double start_time;
741 } img_load_closure;
742
743
744 /* Load a new image into a texture for the given quad.
745  */
746 static void
747 load_quad (ModeInfo *mi, gls_quad *q)
748 {
749   slideshow_state *ss = &sss[MI_SCREEN(mi)];
750   img_load_closure *data;
751
752   if (debug_p)
753     fprintf (stderr, "%s: debug: loading   image %d: %dx%d\n",
754              blurb(), q->texid, mi->xgwa.width, mi->xgwa.height);
755
756   if (q->state != DEAD) abort();
757   if (q->title) free (q->title);
758   q->title = 0;
759
760   if (MI_IS_WIREFRAME(mi))
761     return;
762
763   data = (img_load_closure *) calloc (1, sizeof(*data));
764   data->mi = mi;
765   data->q = q;
766   data->start_time = double_time();
767
768   if (ss->fork_p)
769     {
770       fork_screen_to_ximage (mi->xgwa.screen, mi->window,
771                              slideshow_load_cb, data);
772     }
773   else
774     {
775       char *title = 0;
776       XImage *ximage = screen_to_ximage (mi->xgwa.screen, mi->window, &title);
777       slideshow_load_cb (mi->xgwa.screen, mi->window, ximage, title, data, 0);
778     }
779 }
780
781
782 static void
783 slideshow_load_cb (Screen *screen, Window window, XImage *ximage,
784                    const char *filename, void *closure, double cvt_time)
785 {
786   img_load_closure *data = (img_load_closure *) closure;
787   load_quad_1 (data->mi, data->q, ximage, filename,
788                data->start_time, cvt_time);
789   memset (data, 0, sizeof (*data));
790   free (data);
791 }
792
793
794 void
795 reshape_slideshow (ModeInfo *mi, int width, int height)
796 {
797   slideshow_state *ss = &sss[MI_SCREEN(mi)];
798   GLfloat s;
799   glViewport (0, 0, width, height);
800   glMatrixMode (GL_PROJECTION);
801   glLoadIdentity();
802   glMatrixMode (GL_MODELVIEW);
803   glLoadIdentity();
804
805   s = 2;
806
807   if (debug_p)
808     {
809       s *= (zoom / 100.0) * 0.75;
810       if (s < 0.1) s = 0.1;
811     }
812
813   glScalef (s, s, s);
814   glTranslatef (-0.5, -0.5, 0);
815
816   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
817
818   ss->redisplay_needed_p = True;
819 }
820
821
822 Bool
823 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
824 {
825   slideshow_state *ss = &sss[MI_SCREEN(mi)];
826
827   if (event->xany.type == Expose ||
828       event->xany.type == GraphicsExpose ||
829       event->xany.type == VisibilityNotify)
830     {
831       if (debug_p)
832         fprintf (stderr, "%s: debug: exposure\n", blurb());
833       ss->redisplay_needed_p = True;
834       return True;
835     }
836   return False;
837 }
838
839
840 /* Do some sanity checking on various user-supplied values, and make
841    sure they are all internally consistent.
842  */
843 static void
844 sanity_check (ModeInfo *mi)
845 {
846   if (zoom < 1) zoom = 1;           /* zoom is a positive percentage */
847   else if (zoom > 100) zoom = 100;
848
849   if (pan_seconds < fade_seconds)   /* pan is inclusive of fade */
850     pan_seconds = fade_seconds;
851
852   if (pan_seconds == 0)             /* no zero-length cycles, please... */
853     pan_seconds = 1;
854
855   if (image_seconds < pan_seconds)  /* we only change images at fade-time */
856     image_seconds = pan_seconds;
857
858   /* If we're not panning/zooming within the image, then there's no point
859      in crossfading the image with itself -- only do crossfades when changing
860      to a new image. */
861   if (zoom == 100 && pan_seconds < image_seconds)
862     pan_seconds = image_seconds;
863
864   if      (fps_cutoff < 0)  fps_cutoff = 0;
865   else if (fps_cutoff > 30) fps_cutoff = 30;
866 }
867
868
869 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
870  */
871 static void
872 hack_resources (void)
873 {
874 #if 0
875   char *res = "desktopGrabber";
876   char *val = get_string_resource (res, "DesktopGrabber");
877   char buf1[255];
878   char buf2[255];
879   XrmValue value;
880   sprintf (buf1, "%.100s.%.100s", progclass, res);
881   sprintf (buf2, "%.200s -v", val);
882   value.addr = buf2;
883   value.size = strlen(buf2);
884   XrmPutResource (&db, buf1, "String", &value);
885 #endif
886 }
887
888
889 void
890 init_slideshow (ModeInfo *mi)
891 {
892   int screen = MI_SCREEN(mi);
893   slideshow_state *ss;
894   int wire = MI_IS_WIREFRAME(mi);
895   int i;
896   
897   if (sss == NULL) {
898     if ((sss = (slideshow_state *)
899          calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
900       return;
901   }
902   ss = &sss[screen];
903
904   if ((ss->glx_context = init_GL(mi)) != NULL) {
905     reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
906   } else {
907     MI_CLEARWINDOW(mi);
908   }
909
910   if (debug_p)
911     fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
912              blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
913
914   sanity_check(mi);
915
916   if (debug_p)
917     fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
918              blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
919
920   if (! wire)
921     {
922       glShadeModel (GL_SMOOTH);
923       glPolygonMode (GL_FRONT_AND_BACK,GL_FILL);
924       glEnable (GL_DEPTH_TEST);
925       glEnable (GL_CULL_FACE);
926       glCullFace (GL_FRONT);
927       glDisable (GL_LIGHTING);
928     }
929
930   ss->now = double_time ();
931   ss->dawn_of_time = ss->now;
932
933   if (debug_p) glLineWidth (3);
934
935   ss->pan_start_time   = ss->now;
936   ss->image_start_time = ss->now;
937
938   load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
939
940   for (i = 0; i < countof(ss->texids); i++)
941     glGenTextures (1, &ss->texids[i]);
942   ss->current_texid = 0;
943
944   for (i = 0; i < countof(ss->quads); i++)
945     {
946       gls_quad *q = &ss->quads[i];
947       q->texid = ss->current_texid;
948       q->state = DEAD;
949       reset_quad (mi, q);
950       q->state = DEAD;
951     }
952
953   if (debug_p)
954     hack_resources();
955
956   load_quad (mi, &ss->quads[0]);
957   ss->quads[0].state = IN;
958
959   ss->redisplay_needed_p = True;
960
961   ss->fork_p = 0; /* #### buggy */
962
963 }
964
965
966 /* Call this each time we change from one state to another.
967    It gathers statistics on the frame rate of the previous state,
968    and if it's bad, turn things off (under the assumption that
969    we're running on sucky hardware.)
970  */
971 static void
972 ponder_state_change (ModeInfo *mi)
973 {
974   slideshow_state *ss = &sss[MI_SCREEN(mi)];
975   const char *which;
976   int frames, secs;
977   GLfloat fps;
978
979   if (ss->fade_frame_count && ss->pan_frame_count)
980     abort();  /* one of these should be zero! */
981   else if (ss->fade_frame_count)   /* just finished fading */
982     {
983       which = "faded ";
984       secs = fade_seconds;
985       frames = ss->fade_frame_count;
986       ss->fade_frame_count = 0;
987     }
988   else if (ss->pan_frame_count)   /* just finished panning */
989     {
990       which = "panned";
991       secs = pan_seconds;
992       frames = ss->pan_frame_count;
993       ss->pan_frame_count = 0;
994     }
995   else
996     return;  /* One of those should be non-zero! Maybe we just started,
997                 and the machine is insanely slow. */
998
999   fps = frames / (GLfloat) secs;
1000
1001   if (debug_p)
1002     fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
1003              blurb(), which, frames, secs, fps);
1004
1005
1006   if (fps < fps_cutoff && !ss->low_fps_p)   /* oops, this computer sucks! */
1007     {
1008       int i;
1009
1010       fprintf (stderr,
1011                "%s: only %.1f fps!  Turning off pan/fade to compensate...\n",
1012                blurb(), fps);
1013       zoom = 100;
1014       fade_seconds = 0;
1015       ss->low_fps_p = True;
1016
1017       sanity_check (mi);
1018
1019       /* Reset all quads, and mark only #0 as active. */
1020       for (i = 0; i < countof(ss->quads); i++)
1021         {
1022           gls_quad *q = &ss->quads[i];
1023           q->state = DEAD;
1024           reset_quad (mi, q);
1025           q->texid = ss->current_texid;
1026           q->state = (i == 0 ? IN : DEAD);
1027         }
1028
1029       ss->pan_start_time = ss->now;
1030       ss->redisplay_needed_p = True;
1031
1032       /* Need this in case zoom changed. */
1033       reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1034     }
1035 }
1036
1037
1038 void
1039 draw_slideshow (ModeInfo *mi)
1040 {
1041   slideshow_state *ss = &sss[MI_SCREEN(mi)];
1042   Window w = MI_WINDOW(mi);
1043   double secs;
1044
1045   if (!ss->glx_context)
1046     return;
1047
1048   if (zoom < 100)
1049     ss->redisplay_needed_p = True;
1050
1051   /* States:
1052       0: - A invisible,  B invisible
1053          - A fading in,  B invisible
1054
1055       1: - A opaque,     B invisible
1056          - A fading out, B fading in
1057          - A invisible, gets reset
1058          - A invisible,  B opaque
1059
1060       2: - A invisible,  B opaque
1061          - A fading in,  B fading out
1062          - B invisible, gets reset
1063          - A opaque,     B invisible (goto 1)
1064   */
1065
1066   ss->now = double_time();
1067
1068   secs = ss->now - ss->pan_start_time;
1069
1070   if (secs < fade_seconds)
1071     {
1072       /* We are in the midst of a fade:
1073          one quad is fading in, the other is fading out.
1074          (If this is the very first time, then the one
1075          fading out is already out.)
1076        */
1077       ss->redisplay_needed_p = True;
1078       ss->fade_frame_count++;
1079
1080       if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
1081              (ss->quads[1].state == IN && ss->quads[0].state == OUT) ||
1082              (ss->quads[0].state == IN && ss->quads[1].state == DEAD)))
1083         abort();
1084     }
1085   else if (secs < pan_seconds)
1086     {
1087       /* One quad is visible and in motion, the other is not.
1088       */
1089       if (ss->fade_frame_count != 0)  /* we just switched from fade to pan */
1090         ponder_state_change (mi);
1091       ss->pan_frame_count++;
1092     }
1093   else
1094     {
1095       /* One quad is visible and in motion, the other is not.
1096          It's time to begin fading the visible one out, and the
1097          invisible one in.  (Reset the invisible one first.)
1098        */
1099       gls_quad *vq, *iq;
1100
1101       ponder_state_change (mi);
1102
1103       if (ss->quads[0].state == IN)
1104         {
1105           vq = &ss->quads[0];
1106           iq = &ss->quads[1];
1107         }
1108       else
1109         {
1110           vq = &ss->quads[1];
1111           iq = &ss->quads[0];
1112         }
1113
1114       if (vq->state != IN)   abort();
1115
1116       /* I don't understand why sometimes iq is still OUT and not DEAD. */
1117       if (iq->state == OUT)  iq->state = DEAD;
1118       if (iq->state != DEAD) abort();
1119
1120       vq->state = OUT;
1121
1122       if (ss->image_start_time + image_seconds <= ss->now)
1123         load_quad (mi, iq);
1124
1125       reset_quad (mi, iq);               /* fade invisible in */
1126       iq->texid = ss->current_texid;     /* make sure we're using latest img */
1127
1128       ss->pan_start_time = ss->now;
1129
1130       if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
1131              (ss->quads[1].state == IN && ss->quads[0].state == OUT)))
1132         abort();
1133     }
1134
1135   ss->fps = fps_1 (mi);
1136
1137   if (!ss->redisplay_needed_p)
1138     return;
1139   else if (debug_p && zoom == 100)
1140     fprintf (stderr, "%s: debug: drawing (%d)\n", blurb(),
1141              (int) (ss->now - ss->dawn_of_time));
1142
1143   draw_quads (mi);
1144   ss->redisplay_needed_p = False;
1145
1146   if (mi->fps_p) fps_2(mi);
1147
1148   glFinish();
1149   glXSwapBuffers (MI_DISPLAY (mi), w);
1150 }
1151
1152 #endif /* USE_GL */