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.
5 * First version Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
6 * based on flipscreen3d, Copyright (C) 2001 Ben Buxton (bb@cactii.net).
8 * Almost entirely rewritten by jwz, 21-Jun-2003.
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
20 * - Resizing the window makes everything go black forevermore. No idea why.
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.
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.
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().
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?)
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.
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
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
70 #include <X11/Intrinsic.h>
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
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"
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"
103 # include "xlockmore.h"
106 #define countof(x) (sizeof((x))/sizeof((*x)))
112 #include <sys/time.h>
115 #include "grab-ximage.h"
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 */
130 GLXContext *glx_context;
131 time_t start_time; /* when we started displaying this image */
133 int motion_frames; /* how many frames each pan takes */
134 int fade_frames; /* how many frames fading in/out takes */
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 */
140 int img_w, img_h; /* Size (pixels) of currently-loaded image */
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 */
147 Bool redisplay_needed_p; /* Sometimes we can get away with not
148 re-painting. Tick this if a redisplay
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
157 Bool fork_p; /* threaded image loading; #### still buggy */
164 static slideshow_state *sss = NULL;
167 /* Command-line arguments
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."
177 static int fps_cutoff; /* If the frame-rate falls below this, turn off
179 static Bool do_titles; /* Display image titles. */
180 static Bool debug_p; /* Be loud and do weird things. */
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" },
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},
204 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
210 static char buf[255];
211 time_t now = time ((time_t *) 0);
212 char *ct = (char *) ctime (&now);
213 int n = strlen(progname);
215 strncpy(buf, progname, n);
218 strncpy(buf+n, ct+11, 8);
219 strcpy(buf+n+9, ": ");
224 /* Returns the current time in seconds as a double.
230 # ifdef GETTIMEOFDAY_TWO_ARGS
232 gettimeofday(&now, &tzp);
237 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
242 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
244 const char *font = get_string_resource (res, "Font");
249 if (!font) font = "-*-times-bold-r-normal-*-180-*";
251 f = XLoadQueryFont(mi->dpy, font);
252 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
255 first = f->min_char_or_byte2;
256 last = f->max_char_or_byte2;
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");
269 print_title_string (ModeInfo *mi, const char *string, GLfloat x, GLfloat y)
271 slideshow_state *ss = &sss[MI_SCREEN(mi)];
272 XFontStruct *font = ss->xfont;
273 GLfloat line_height = font->ascent + font->descent;
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);
282 glMatrixMode(GL_PROJECTION);
287 glMatrixMode(GL_MODELVIEW);
294 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
296 glRasterPos2f (x, y);
297 for (i = 0; i < strlen(string); i++)
302 glRasterPos2f (x, (y -= line_height));
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);
316 glMatrixMode(GL_PROJECTION);
321 glMatrixMode(GL_MODELVIEW);
326 draw_quad (ModeInfo *mi, gls_quad *q)
328 slideshow_state *ss = &sss[MI_SCREEN(mi)];
329 int wire = MI_IS_WIREFRAME(mi);
337 if (q->state == DEAD)
340 secs = ss->now - ss->pan_start_time;
345 ratio = secs / (pan_seconds + fade_seconds);
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);
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. */
357 opacity = 1 - ((secs - pan_seconds) /
358 (GLfloat) fade_seconds); /* fading in or out... */
360 if (q->state == OUT && opacity < 0.0001)
365 glTranslatef (current.x, current.y, 0);
366 glScalef (current.w, current.h, 1);
370 texw = mi->xgwa.width / (GLfloat) ss->img_w;
371 texh = mi->xgwa.height / (GLfloat) ss->img_h;
373 glEnable (GL_TEXTURE_2D);
375 glBindTexture (GL_TEXTURE_2D, q->texid);
376 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
377 glDepthMask (GL_FALSE);
379 /* Draw the texture quad
381 glColor4f (1, 1, 1, opacity);
382 glNormal3f (0, 0, 1);
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);
390 glDisable (GL_TEXTURE_2D);
391 glDisable (GL_BLEND);
395 glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
396 (q->texid == ss->texids[0] ? 0 : opacity),
399 glColor4f (1, 1, 1, opacity);
402 /* Draw a grid inside the box
409 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
410 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
412 for (y = 0; y < 1+d; y += d)
413 for (x = 0; x < 1+d; x += d)
415 glVertex3f (0, y, 0); glVertex3f (1, y, 0);
416 glVertex3f (x, 0, 0); glVertex3f (x, 1, 0);
423 q->title && *q->title)
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
430 glColor4f (opacity, opacity, opacity, 1);
431 print_title_string (mi, q->title,
432 10, mi->xgwa.height - 10);
439 /* Draw the "from" and "to" boxes
441 glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
442 (q->texid == ss->texids[0] ? 0 : opacity),
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);
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);
463 draw_quads (ModeInfo *mi)
465 slideshow_state *ss = &sss[MI_SCREEN(mi)];
469 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
475 glTranslatef (o, o, 0);
478 for (i = 0; i < countof(ss->quads); i++)
479 draw_quad (mi, &ss->quads[i]);
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);
496 /* Re-randomize the state of the given quad.
499 reset_quad (ModeInfo *mi, gls_quad *q)
501 /* slideshow_state *ss = &sss[MI_SCREEN(mi)];*/
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;
508 GLfloat small = mid_w + frand ((1 - mid_w) * 0.3);
510 GLfloat large = small + frand ((1 - small) / 2) + ((1 - small) / 2);
512 GLfloat large = small + frand (1 - small);
515 if (q->state != DEAD)
516 abort(); /* we should only be resetting a quad when it's not visible. */
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.
528 q->from.w = small; q->from.h = small;
529 q->to.w = large; q->to.h = large;
533 q->from.w = large; q->from.h = large;
534 q->to.w = small; q->to.h = small;
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);
546 /* Shrinks the XImage by a factor of two.
549 shrink_image (ModeInfo *mi, XImage *ximage)
551 int w2 = ximage->width/2;
552 int h2 = ximage->height/2;
556 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
560 fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
561 blurb(), ximage->width, ximage->height, w2, h2);
563 ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
566 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
569 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
570 blurb(), ximage->width, ximage->height, w2, h2);
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));
583 /* Load a new image into a texture for the given quad.
586 load_quad_1 (ModeInfo *mi, gls_quad *q, XImage *ximage,
587 const char *filename, double start_time, double cvt_time)
589 slideshow_state *ss = &sss[MI_SCREEN(mi)];
591 int max_reduction = 7;
593 int wire = MI_IS_WIREFRAME(mi);
594 double load_time=0, mipmap_time=0; /* for debugging messages */
596 /* if (q->state != DEAD) abort(); */
598 /* Figure out which texid is currently in use, and pick the other one.
603 if (ss->current_texid == 0)
606 for (i = 0; i < countof(ss->texids); i++)
607 if (ss->texids[i] != ss->current_texid)
613 if (tid == 0) abort(); /* both textures in use by visible quads? */
615 ss->current_texid = tid;
621 if (q->title) free (q->title);
622 q->title = (filename ? strdup (filename) : 0);
624 if (q->title) /* strip filename to part after last /. */
626 char *s = strrchr (q->title, '/');
627 if (s) strcpy (q->title, s+1);
632 fprintf (stderr, "%s: debug: loaded image %d: \"%s\"\n",
633 blurb(), q->texid, (q->title ? q->title : "(null)"));
634 load_time = double_time();
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);
642 ss->img_w = ximage->width;
643 ss->img_h = ximage->height;
648 status = gluBuild2DMipmaps (GL_TEXTURE_2D, 3,
649 ximage->width, ximage->height,
650 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
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. */
660 const char *s = (char *) gluErrorString (status);
664 sprintf (buf, "unknown error %d", status);
670 if (++err_count > max_reduction)
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,
687 fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
688 blurb(), ximage->width, ximage->height, s);
689 shrink_image (mi, ximage);
694 check_gl_error("mipmapping"); /* should get a return code instead of a
695 GL error, but just in case... */
699 XDestroyImage(ximage);
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();
709 cvt_time = load_time;
712 "%s: debug: load time elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
714 cvt_time - start_time,
715 load_time - cvt_time,
716 mipmap_time - load_time,
717 mipmap_time - start_time);
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.
726 ss->now = double_time ();
727 ss->image_start_time = ss->now;
729 ss->redisplay_needed_p = True;
733 static void slideshow_load_cb (Screen *, Window, XImage *,
734 const char *filename, void *closure,
744 /* Load a new image into a texture for the given quad.
747 load_quad (ModeInfo *mi, gls_quad *q)
749 slideshow_state *ss = &sss[MI_SCREEN(mi)];
750 img_load_closure *data;
753 fprintf (stderr, "%s: debug: loading image %d: %dx%d\n",
754 blurb(), q->texid, mi->xgwa.width, mi->xgwa.height);
756 if (q->state != DEAD) abort();
757 if (q->title) free (q->title);
760 if (MI_IS_WIREFRAME(mi))
763 data = (img_load_closure *) calloc (1, sizeof(*data));
766 data->start_time = double_time();
770 fork_screen_to_ximage (mi->xgwa.screen, mi->window,
771 slideshow_load_cb, data);
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);
783 slideshow_load_cb (Screen *screen, Window window, XImage *ximage,
784 const char *filename, void *closure, double cvt_time)
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));
795 reshape_slideshow (ModeInfo *mi, int width, int height)
797 slideshow_state *ss = &sss[MI_SCREEN(mi)];
799 glViewport (0, 0, width, height);
800 glMatrixMode (GL_PROJECTION);
802 glMatrixMode (GL_MODELVIEW);
809 s *= (zoom / 100.0) * 0.75;
810 if (s < 0.1) s = 0.1;
814 glTranslatef (-0.5, -0.5, 0);
816 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
818 ss->redisplay_needed_p = True;
823 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
825 slideshow_state *ss = &sss[MI_SCREEN(mi)];
827 if (event->xany.type == Expose ||
828 event->xany.type == GraphicsExpose ||
829 event->xany.type == VisibilityNotify)
832 fprintf (stderr, "%s: debug: exposure\n", blurb());
833 ss->redisplay_needed_p = True;
840 /* Do some sanity checking on various user-supplied values, and make
841 sure they are all internally consistent.
844 sanity_check (ModeInfo *mi)
846 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
847 else if (zoom > 100) zoom = 100;
849 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
850 pan_seconds = fade_seconds;
852 if (pan_seconds == 0) /* no zero-length cycles, please... */
855 if (image_seconds < pan_seconds) /* we only change images at fade-time */
856 image_seconds = pan_seconds;
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
861 if (zoom == 100 && pan_seconds < image_seconds)
862 pan_seconds = image_seconds;
864 if (fps_cutoff < 0) fps_cutoff = 0;
865 else if (fps_cutoff > 30) fps_cutoff = 30;
869 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
872 hack_resources (void)
875 char *res = "desktopGrabber";
876 char *val = get_string_resource (res, "DesktopGrabber");
880 sprintf (buf1, "%.100s.%.100s", progclass, res);
881 sprintf (buf2, "%.200s -v", val);
883 value.size = strlen(buf2);
884 XrmPutResource (&db, buf1, "String", &value);
890 init_slideshow (ModeInfo *mi)
892 int screen = MI_SCREEN(mi);
894 int wire = MI_IS_WIREFRAME(mi);
898 if ((sss = (slideshow_state *)
899 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
904 if ((ss->glx_context = init_GL(mi)) != NULL) {
905 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
911 fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
912 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
917 fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
918 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
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);
930 ss->now = double_time ();
931 ss->dawn_of_time = ss->now;
933 if (debug_p) glLineWidth (3);
935 ss->pan_start_time = ss->now;
936 ss->image_start_time = ss->now;
938 load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
940 for (i = 0; i < countof(ss->texids); i++)
941 glGenTextures (1, &ss->texids[i]);
942 ss->current_texid = 0;
944 for (i = 0; i < countof(ss->quads); i++)
946 gls_quad *q = &ss->quads[i];
947 q->texid = ss->current_texid;
956 load_quad (mi, &ss->quads[0]);
957 ss->quads[0].state = IN;
959 ss->redisplay_needed_p = True;
961 ss->fork_p = 0; /* #### buggy */
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.)
972 ponder_state_change (ModeInfo *mi)
974 slideshow_state *ss = &sss[MI_SCREEN(mi)];
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 */
985 frames = ss->fade_frame_count;
986 ss->fade_frame_count = 0;
988 else if (ss->pan_frame_count) /* just finished panning */
992 frames = ss->pan_frame_count;
993 ss->pan_frame_count = 0;
996 return; /* One of those should be non-zero! Maybe we just started,
997 and the machine is insanely slow. */
999 fps = frames / (GLfloat) secs;
1002 fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
1003 blurb(), which, frames, secs, fps);
1006 if (fps < fps_cutoff && !ss->low_fps_p) /* oops, this computer sucks! */
1011 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1015 ss->low_fps_p = True;
1019 /* Reset all quads, and mark only #0 as active. */
1020 for (i = 0; i < countof(ss->quads); i++)
1022 gls_quad *q = &ss->quads[i];
1025 q->texid = ss->current_texid;
1026 q->state = (i == 0 ? IN : DEAD);
1029 ss->pan_start_time = ss->now;
1030 ss->redisplay_needed_p = True;
1032 /* Need this in case zoom changed. */
1033 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1039 draw_slideshow (ModeInfo *mi)
1041 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1042 Window w = MI_WINDOW(mi);
1045 if (!ss->glx_context)
1049 ss->redisplay_needed_p = True;
1052 0: - A invisible, B invisible
1053 - A fading in, B invisible
1055 1: - A opaque, B invisible
1056 - A fading out, B fading in
1057 - A invisible, gets reset
1058 - A invisible, B opaque
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)
1066 ss->now = double_time();
1068 secs = ss->now - ss->pan_start_time;
1070 if (secs < fade_seconds)
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.)
1077 ss->redisplay_needed_p = True;
1078 ss->fade_frame_count++;
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)))
1085 else if (secs < pan_seconds)
1087 /* One quad is visible and in motion, the other is not.
1089 if (ss->fade_frame_count != 0) /* we just switched from fade to pan */
1090 ponder_state_change (mi);
1091 ss->pan_frame_count++;
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.)
1101 ponder_state_change (mi);
1103 if (ss->quads[0].state == IN)
1114 if (vq->state != IN) abort();
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();
1122 if (ss->image_start_time + image_seconds <= ss->now)
1125 reset_quad (mi, iq); /* fade invisible in */
1126 iq->texid = ss->current_texid; /* make sure we're using latest img */
1128 ss->pan_start_time = ss->now;
1130 if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
1131 (ss->quads[1].state == IN && ss->quads[0].state == OUT)))
1135 ss->fps = fps_1 (mi);
1137 if (!ss->redisplay_needed_p)
1139 else if (debug_p && zoom == 100)
1140 fprintf (stderr, "%s: debug: drawing (%d)\n", blurb(),
1141 (int) (ss->now - ss->dawn_of_time));
1144 ss->redisplay_needed_p = False;
1146 if (mi->fps_p) fps_2(mi);
1149 glXSwapBuffers (MI_DISPLAY (mi), w);