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