1 /* glslideshow, Copyright (c) 2003-2011 Jamie Zawinski <jwz@jwz.org>
2 * Loads a sequence of images and smoothly pans around them; crossfades
3 * when loading new images.
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.
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
17 *****************************************************************************
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...
27 * Image loading happens in three stages:
29 * 1: Fork a process and run xscreensaver-getimage in the background.
30 * This writes image data to a server-side X pixmap.
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
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
40 * 4: We must actually construct a texture.
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.
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
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.)
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.
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.
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?
71 #define DEFAULTS "*delay: 20000 \n" \
72 "*wireframe: False \n" \
73 "*showFPS: False \n" \
74 "*fpsSolid: True \n" \
76 "*titleFont: -*-helvetica-medium-r-normal-*-180-*\n" \
77 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
78 "*grabDesktopImages: False \n" \
79 "*chooseRandomImages: True \n"
81 # define refresh_slideshow 0
82 # define release_slideshow 0
83 # include "xlockmore.h"
86 #define countof(x) (sizeof((x))/sizeof((*x)))
91 # define DEF_FADE_DURATION "2"
92 # define DEF_PAN_DURATION "6"
93 # define DEF_IMAGE_DURATION "30"
94 # define DEF_ZOOM "75"
95 # define DEF_FPS_CUTOFF "5"
96 # define DEF_TITLES "False"
97 # define DEF_LETTERBOX "True"
98 # define DEF_DEBUG "False"
99 # define DEF_MIPMAP "True"
101 #include "grab-ximage.h"
102 #include "glxfonts.h"
110 int id; /* unique number for debugging */
111 char *title; /* the filename of this image */
112 int w, h; /* size in pixels of the image */
113 int tw, th; /* size in pixels of the texture */
114 XRectangle geom; /* where in the image the bits are */
115 Bool loaded_p; /* whether the image has finished loading */
116 Bool used_p; /* whether the image has yet appeared
118 GLuint texid; /* which texture contains the image */
119 int refcount; /* how many sprites refer to this image */
123 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
126 int id; /* unique number for debugging */
127 image *img; /* which image this animation displays */
128 GLfloat opacity; /* how to render it */
129 double start_time; /* when this animation began */
130 rect from, to, current; /* the journey this image is taking */
131 sprite_state state; /* the state we're in right now */
132 sprite_state prev_state; /* the state we were in previously */
133 double state_time; /* time of last state change */
134 int frame_count; /* frames since last state change */
139 GLXContext *glx_context;
140 int nimages; /* how many images are loaded or loading now */
141 image *images[10]; /* pointers to the images */
143 int nsprites; /* how many sprites are animating right now */
144 sprite *sprites[10]; /* pointers to the live sprites */
146 double now; /* current time in seconds */
147 double dawn_of_time; /* when the program launched */
148 double image_load_time; /* time when we last loaded a new image */
149 double prev_frame_time; /* time when we last drew a frame */
151 Bool awaiting_first_image_p; /* Early in startup: nothing to display yet */
152 Bool redisplay_needed_p; /* Sometimes we can get away with not
153 re-painting. Tick this if a redisplay
155 Bool change_now_p; /* Set when the user clicks to ask for a new
158 GLfloat fps; /* approximate frame rate we're achieving */
159 GLfloat theoretical_fps; /* maximum frame rate that might be possible */
160 Bool checked_fps_p; /* Whether we have checked for a low
163 XFontStruct *xfont; /* for printing image file names */
166 int sprite_id, image_id; /* debugging id counters */
173 static slideshow_state *sss = NULL;
176 /* Command-line arguments
178 static int fade_seconds; /* Duration in seconds of fade transitions.
179 If 0, jump-cut instead of fading. */
180 static int pan_seconds; /* Duration of each pan through an image. */
181 static int image_seconds; /* How many seconds until loading a new image. */
182 static int zoom; /* How far in to zoom when panning, in percent of
183 image size: that is, 75 means "when zoomed all
184 the way in, 75% of the image will be visible."
186 static int fps_cutoff; /* If the frame-rate falls below this, turn off
188 static Bool letterbox_p; /* When a loaded image is not the same aspect
189 ratio as the window, whether to display black
192 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
193 static Bool do_titles; /* Display image titles. */
194 static Bool debug_p; /* Be loud and do weird things. */
197 static XrmOptionDescRec opts[] = {
198 {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
199 {"-pan", ".panDuration", XrmoptionSepArg, 0 },
200 {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
201 {"-zoom", ".zoom", XrmoptionSepArg, 0 },
202 {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
203 {"-titles", ".titles", XrmoptionNoArg, "True" },
204 {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
205 {"-no-letterbox", ".letterbox", XrmoptionNoArg, "False" },
206 {"-clip", ".letterbox", XrmoptionNoArg, "False" },
207 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
208 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
209 {"-debug", ".debug", XrmoptionNoArg, "True" },
212 static argtype vars[] = {
213 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
214 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
215 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
216 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
217 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
218 { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
219 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
220 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
221 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
224 ENTRYPOINT ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
231 return "GLSlideshow";
233 static char buf[255];
234 time_t now = time ((time_t *) 0);
235 char *ct = (char *) ctime (&now);
236 int n = strlen(progname);
238 strncpy(buf, progname, n);
241 strncpy(buf+n, ct+11, 8);
242 strcpy(buf+n+9, ": ");
248 /* Returns the current time in seconds as a double.
254 # ifdef GETTIMEOFDAY_TWO_ARGS
256 gettimeofday(&now, &tzp);
261 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
265 static void image_loaded_cb (const char *filename, XRectangle *geom,
266 int image_width, int image_height,
267 int texture_width, int texture_height,
271 /* Allocate an image structure and start a file loading in the background.
274 alloc_image (ModeInfo *mi)
276 slideshow_state *ss = &sss[MI_SCREEN(mi)];
277 int wire = MI_IS_WIREFRAME(mi);
278 image *img = (image *) calloc (1, sizeof (*img));
280 img->id = ++ss->image_id;
281 img->loaded_p = False;
285 glGenTextures (1, &img->texid);
286 if (img->texid <= 0) abort();
288 ss->image_load_time = ss->now;
291 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
293 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
294 0, 0, mipmap_p, img->texid, image_loaded_cb, img);
296 ss->images[ss->nimages++] = img;
297 if (ss->nimages >= countof(ss->images)) abort();
303 /* Callback that tells us that the texture has been loaded.
306 image_loaded_cb (const char *filename, XRectangle *geom,
307 int image_width, int image_height,
308 int texture_width, int texture_height,
311 image *img = (image *) closure;
312 ModeInfo *mi = img->mi;
313 /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
315 int wire = MI_IS_WIREFRAME(mi);
319 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
320 img->h = MI_HEIGHT (mi);
321 img->geom.width = img->w;
322 img->geom.height = img->h;
326 if (image_width == 0 || image_height == 0)
329 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
330 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
331 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
333 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
334 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
336 img->w = image_width;
337 img->h = image_height;
338 img->tw = texture_width;
339 img->th = texture_height;
341 img->title = (filename ? strdup (filename) : 0);
343 /* If the image's width doesn't come back as the width of the screen,
344 then the image must have been scaled down (due to insufficient
345 texture memory.) Scale up the coordinates to stretch the image
348 if (img->w != MI_WIDTH(mi))
350 double scale = (double) MI_WIDTH(mi) / img->w;
355 img->geom.x *= scale;
356 img->geom.y *= scale;
357 img->geom.width *= scale;
358 img->geom.height *= scale;
361 # if 0 /* xscreensaver-getimage returns paths relative to the image directory
362 now, so leave the sub-directory part in.
364 if (img->title) /* strip filename to part between last "/" and last ".". */
366 char *s = strrchr (img->title, '/');
367 if (s) strcpy (img->title, s+1);
368 s = strrchr (img->title, '.');
374 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
375 blurb(), img->id, (img->title ? img->title : "(null)"));
378 img->loaded_p = True;
383 /* Free the image and texture, after nobody is referencing it.
386 destroy_image (ModeInfo *mi, image *img)
388 slideshow_state *ss = &sss[MI_SCREEN(mi)];
389 Bool freed_p = False;
393 if (!img->loaded_p) abort();
394 if (!img->used_p) abort();
395 if (img->texid <= 0) abort();
396 if (img->refcount != 0) abort();
398 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
399 if (ss->images[i] == img)
402 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
403 ss->images[j] = ss->images[j+1];
410 if (!freed_p) abort();
413 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
414 blurb(), img->id, (img->title ? img->title : "(null)"));
416 if (img->title) free (img->title);
417 glDeleteTextures (1, &img->texid);
422 /* Return an image to use for a sprite.
423 If it's time for a new one, get a new one.
424 Otherwise, use an old one.
425 Might return 0 if the machine is really slow.
428 get_image (ModeInfo *mi)
430 slideshow_state *ss = &sss[MI_SCREEN(mi)];
432 double now = ss->now;
433 Bool want_new_p = (ss->change_now_p ||
434 ss->image_load_time + image_seconds <= now);
437 image *loading_img = 0;
440 for (i = 0; i < ss->nimages; i++)
442 image *img2 = ss->images[i];
446 else if (!img2->used_p)
452 if (want_new_p && new_img)
453 img = new_img, new_img = 0, ss->change_now_p = False;
455 img = old_img, old_img = 0;
457 img = new_img, new_img = 0, ss->change_now_p = False;
459 /* Make sure that there is always one unused image in the pipe.
461 if (!new_img && !loading_img)
468 /* Pick random starting and ending positions for the given sprite.
471 randomize_sprite (ModeInfo *mi, sprite *sp)
473 int vp_w = MI_WIDTH(mi);
474 int vp_h = MI_HEIGHT(mi);
475 int img_w = sp->img->geom.width;
476 int img_h = sp->img->geom.height;
478 double ratio = (double) img_h / img_w;
489 min_w = img_w * (float) vp_h / img_h;
492 max_w = min_w * 100 / zoom;
494 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
495 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
496 sp->from.h = sp->from.w * ratio;
497 sp->to.h = sp->to.w * ratio;
499 if (zoom == 100) /* only one box, and it is centered */
501 sp->from.x = (sp->from.w > vp_w
502 ? -(sp->from.w - vp_w) / 2
503 : (vp_w - sp->from.w) / 2);
504 sp->from.y = (sp->from.h > vp_h
505 ? -(sp->from.h - vp_h) / 2
506 : (vp_h - sp->from.h) / 2);
509 else /* position both boxes randomly */
511 sp->from.x = (sp->from.w > vp_w
512 ? -frand (sp->from.w - vp_w)
513 : frand (vp_w - sp->from.w));
514 sp->from.y = (sp->from.h > vp_h
515 ? -frand (sp->from.h - vp_h)
516 : frand (vp_h - sp->from.h));
517 sp->to.x = (sp->to.w > vp_w
518 ? -frand (sp->to.w - vp_w)
519 : frand (vp_w - sp->to.w));
520 sp->to.y = (sp->to.h > vp_h
521 ? -frand (sp->to.h - vp_h)
522 : frand (vp_h - sp->to.h));
532 /* Make sure the aspect ratios are within 0.001 of each other.
535 int r1 = 0.5 + (sp->from.w * 1000 / sp->from.h);
536 int r2 = 0.5 + (sp->to.w * 1000 / sp->to.h);
537 if (r1 < r2-1 || r1 > r2+1)
540 "%s: botched aspect: %f x %f (%d) vs %f x %f (%d): %s\n",
542 sp->from.w, sp->from.h, r1,
543 sp->to.w, sp->to.h, r2,
544 (sp->img->title ? sp->img->title : "[null]"));
560 /* Allocate a new sprite and start its animation going.
563 new_sprite (ModeInfo *mi)
565 slideshow_state *ss = &sss[MI_SCREEN(mi)];
566 image *img = get_image (mi);
571 /* Oops, no images yet! The machine is probably hurting bad.
572 Let's give it some time before thrashing again. */
577 sp = (sprite *) calloc (1, sizeof (*sp));
578 sp->id = ++ss->sprite_id;
579 sp->start_time = ss->now;
580 sp->state_time = sp->start_time;
581 sp->state = sp->prev_state = NEW;
585 sp->img->used_p = True;
587 ss->sprites[ss->nsprites++] = sp;
588 if (ss->nsprites >= countof(ss->sprites)) abort();
590 randomize_sprite (mi, sp);
596 /* Free the given sprite, and decrement the reference count on its image.
599 destroy_sprite (ModeInfo *mi, sprite *sp)
601 slideshow_state *ss = &sss[MI_SCREEN(mi)];
602 Bool freed_p = False;
607 if (sp->state != DEAD) abort();
610 if (!img->loaded_p) abort();
611 if (!img->used_p) abort();
612 if (img->refcount <= 0) abort();
614 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
615 if (ss->sprites[i] == sp)
618 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
619 ss->sprites[j] = ss->sprites[j+1];
626 if (!freed_p) abort();
631 if (img->refcount < 0) abort();
632 if (img->refcount == 0)
633 destroy_image (mi, img);
637 /* Updates the sprite for the current frame of the animation based on
638 its creation time compared to the current wall clock.
641 tick_sprite (ModeInfo *mi, sprite *sp)
643 slideshow_state *ss = &sss[MI_SCREEN(mi)];
644 image *img = sp->img;
645 double now = ss->now;
648 rect prev_rect = sp->current;
649 GLfloat prev_opacity = sp->opacity;
651 if (! sp->img) abort();
652 if (! img->loaded_p) abort();
654 secs = now - sp->start_time;
655 ratio = secs / (pan_seconds + fade_seconds);
656 if (ratio > 1) ratio = 1;
658 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
659 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
660 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
661 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
663 sp->prev_state = sp->state;
665 if (secs < fade_seconds)
668 sp->opacity = secs / (GLfloat) fade_seconds;
670 else if (secs < pan_seconds)
675 else if (secs < pan_seconds + fade_seconds)
678 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
686 if (sp->state != sp->prev_state &&
687 (sp->prev_state == IN ||
688 sp->prev_state == FULL))
690 double secs = now - sp->state_time;
694 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
696 (sp->prev_state == IN ? "fade" : "pan "),
699 sp->frame_count / secs,
700 ss->theoretical_fps);
702 sp->state_time = now;
708 if (sp->state != DEAD &&
709 (prev_rect.x != sp->current.x ||
710 prev_rect.y != sp->current.y ||
711 prev_rect.w != sp->current.w ||
712 prev_rect.h != sp->current.h ||
713 prev_opacity != sp->opacity))
714 ss->redisplay_needed_p = True;
718 /* Draw the given sprite at the phase of its animation dictated by
719 its creation time compared to the current wall clock.
722 draw_sprite (ModeInfo *mi, sprite *sp)
724 slideshow_state *ss = &sss[MI_SCREEN(mi)];
725 int wire = MI_IS_WIREFRAME(mi);
726 image *img = sp->img;
728 if (! sp->img) abort();
729 if (! img->loaded_p) abort();
733 glTranslatef (sp->current.x, sp->current.y, 0);
734 glScalef (sp->current.w, sp->current.h, 1);
736 if (wire) /* Draw a grid inside the box */
739 GLfloat dx = dy * img->w / img->h;
743 glColor4f (sp->opacity, 0, 0, 1);
745 glColor4f (0, 0, sp->opacity, 1);
748 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
749 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
751 for (y = 0; y < 1+dy; y += dy)
753 GLfloat yy = (y > 1 ? 1 : y);
754 for (x = 0.5; x < 1+dx; x += dx)
756 GLfloat xx = (x > 1 ? 1 : x);
757 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
758 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
760 for (x = 0.5; x > -dx; x -= dx)
762 GLfloat xx = (x < 0 ? 0 : x);
763 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
764 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
769 else /* Draw the texture quad */
771 GLfloat texw = img->geom.width / (GLfloat) img->tw;
772 GLfloat texh = img->geom.height / (GLfloat) img->th;
773 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
774 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
775 GLfloat texx2 = texx1 + texw;
776 GLfloat texy2 = texy1 + texh;
778 glBindTexture (GL_TEXTURE_2D, img->texid);
779 glColor4f (1, 1, 1, sp->opacity);
780 glNormal3f (0, 0, 1);
782 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
783 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
784 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
785 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
788 if (debug_p) /* Draw a border around the image */
790 if (!wire) glDisable (GL_TEXTURE_2D);
793 glColor4f (sp->opacity, 0, 0, 1);
795 glColor4f (0, 0, sp->opacity, 1);
797 glBegin (GL_LINE_LOOP);
798 glVertex3f (0, 0, 0);
799 glVertex3f (0, 1, 0);
800 glVertex3f (1, 1, 0);
801 glVertex3f (1, 0, 0);
804 if (!wire) glEnable (GL_TEXTURE_2D);
810 img->title && *img->title)
813 int y = mi->xgwa.height - 10;
814 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
815 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
816 mi->xgwa.width, mi->xgwa.height, x, y,
819 glColor4f (1, 1, 1, sp->opacity);
820 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
821 mi->xgwa.width, mi->xgwa.height, x, y,
829 if (!wire) glDisable (GL_TEXTURE_2D);
832 glColor4f (1, 0, 0, 1);
834 glColor4f (0, 0, 1, 1);
836 /* Draw the "from" and "to" boxes
838 glBegin (GL_LINE_LOOP);
839 glVertex3f (sp->from.x, sp->from.y, 0);
840 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
841 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
842 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
845 glBegin (GL_LINE_LOOP);
846 glVertex3f (sp->to.x, sp->to.y, 0);
847 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
848 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
849 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
852 if (!wire) glEnable (GL_TEXTURE_2D);
858 tick_sprites (ModeInfo *mi)
860 slideshow_state *ss = &sss[MI_SCREEN(mi)];
862 for (i = 0; i < ss->nsprites; i++)
863 tick_sprite (mi, ss->sprites[i]);
868 draw_sprites (ModeInfo *mi)
870 slideshow_state *ss = &sss[MI_SCREEN(mi)];
873 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
876 for (i = 0; i < ss->nsprites; i++)
877 draw_sprite (mi, ss->sprites[i]);
880 if (debug_p) /* draw a white box (the "screen") */
882 int wire = MI_IS_WIREFRAME(mi);
884 if (!wire) glDisable (GL_TEXTURE_2D);
886 glColor4f (1, 1, 1, 1);
887 glBegin (GL_LINE_LOOP);
888 glVertex3f (0, 0, 0);
889 glVertex3f (0, 1, 0);
890 glVertex3f (1, 1, 0);
891 glVertex3f (1, 0, 0);
894 if (!wire) glEnable (GL_TEXTURE_2D);
900 reshape_slideshow (ModeInfo *mi, int width, int height)
902 slideshow_state *ss = &sss[MI_SCREEN(mi)];
904 glViewport (0, 0, width, height);
905 glMatrixMode (GL_PROJECTION);
907 glMatrixMode (GL_MODELVIEW);
914 s *= (zoom / 100.0) * 0.75;
915 if (s < 0.1) s = 0.1;
919 glTranslatef (-0.5, -0.5, 0);
921 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
923 ss->redisplay_needed_p = True;
928 slideshow_handle_event (ModeInfo *mi, XEvent *event)
930 slideshow_state *ss = &sss[MI_SCREEN(mi)];
932 if (event->xany.type == ButtonPress &&
933 event->xbutton.button == Button1)
935 ss->change_now_p = True;
938 else if (event->xany.type == KeyPress)
942 XLookupString (&event->xkey, &c, 1, &keysym, 0);
943 if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
945 ss->change_now_p = True;
949 else if (event->xany.type == Expose ||
950 event->xany.type == GraphicsExpose ||
951 event->xany.type == VisibilityNotify)
953 ss->redisplay_needed_p = True;
955 fprintf (stderr, "%s: exposure\n", blurb());
963 /* Do some sanity checking on various user-supplied values, and make
964 sure they are all internally consistent.
967 sanity_check (ModeInfo *mi)
969 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
970 else if (zoom > 100) zoom = 100;
972 if (zoom == 100) /* with no zooming, there is no panning */
975 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
976 pan_seconds = fade_seconds;
978 if (pan_seconds == 0) /* no zero-length cycles, please... */
981 if (image_seconds < pan_seconds) /* we only change images at fade-time */
982 image_seconds = pan_seconds;
984 /* If we're not panning/zooming within the image, then there's no point
985 in crossfading the image with itself -- only do crossfades when changing
987 if (zoom == 100 && pan_seconds < image_seconds)
988 pan_seconds = image_seconds;
990 /* No need to use mipmaps if we're not changing the image size much */
991 if (zoom >= 80) mipmap_p = False;
993 if (fps_cutoff < 0) fps_cutoff = 0;
994 else if (fps_cutoff > 30) fps_cutoff = 30;
999 check_fps (ModeInfo *mi)
1001 #ifndef HAVE_COCOA /* always assume Cocoa is fast enough */
1003 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1005 double start_time, end_time, wall_elapsed, frame_duration, fps;
1008 start_time = ss->now;
1009 end_time = double_time();
1010 frame_duration = end_time - start_time; /* time spent drawing this frame */
1011 ss->time_elapsed += frame_duration; /* time spent drawing all frames */
1012 ss->frames_elapsed++;
1014 wall_elapsed = end_time - ss->dawn_of_time;
1015 fps = ss->frames_elapsed / ss->time_elapsed;
1016 ss->theoretical_fps = fps;
1018 if (ss->checked_fps_p) return;
1020 if (wall_elapsed <= 8) /* too early to be sure */
1023 ss->checked_fps_p = True;
1025 if (fps >= fps_cutoff)
1029 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1030 blurb(), fps, ss->frames_elapsed, wall_elapsed);
1035 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1042 for (i = 0; i < ss->nsprites; i++)
1044 sprite *sp = ss->sprites[i];
1045 randomize_sprite (mi, sp);
1049 ss->redisplay_needed_p = True;
1051 /* Need this in case zoom changed. */
1052 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1053 #endif /* HAVE_COCOA */
1057 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1060 hack_resources (void)
1063 char *res = "desktopGrabber";
1064 char *val = get_string_resource (res, "DesktopGrabber");
1068 sprintf (buf1, "%.100s.%.100s", progclass, res);
1069 sprintf (buf2, "%.200s -v", val);
1071 value.size = strlen(buf2);
1072 XrmPutResource (&db, buf1, "String", &value);
1078 init_slideshow (ModeInfo *mi)
1080 int screen = MI_SCREEN(mi);
1081 slideshow_state *ss;
1082 int wire = MI_IS_WIREFRAME(mi);
1085 if ((sss = (slideshow_state *)
1086 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1091 if ((ss->glx_context = init_GL(mi)) != NULL) {
1092 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1098 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1099 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1104 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1105 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1107 glDisable (GL_LIGHTING);
1108 glDisable (GL_DEPTH_TEST);
1109 glDepthMask (GL_FALSE);
1110 glEnable (GL_CULL_FACE);
1111 glCullFace (GL_BACK);
1115 glEnable (GL_TEXTURE_2D);
1116 glShadeModel (GL_SMOOTH);
1117 glEnable (GL_BLEND);
1118 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1121 if (debug_p) glLineWidth (3);
1123 load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1128 ss->now = double_time();
1129 ss->dawn_of_time = ss->now;
1130 ss->prev_frame_time = ss->now;
1132 ss->awaiting_first_image_p = True;
1138 draw_slideshow (ModeInfo *mi)
1140 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1143 if (!ss->glx_context)
1146 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
1148 if (ss->awaiting_first_image_p)
1150 image *img = ss->images[0];
1155 ss->awaiting_first_image_p = False;
1156 ss->dawn_of_time = double_time();
1158 /* start the very first sprite fading in */
1162 ss->now = double_time();
1164 /* Each sprite has three states: fading in, full, fading out.
1165 The in/out states overlap like this:
1167 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1168 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1169 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1171 So as soon as a sprite goes into the "out" state, we create
1172 a new sprite (in the "in" state.)
1175 if (ss->nsprites > 2) abort();
1177 /* If a sprite is just entering the fade-out state,
1178 then add a new sprite in the fade-in state.
1180 for (i = 0; i < ss->nsprites; i++)
1182 sprite *sp = ss->sprites[i];
1183 if (sp->state != sp->prev_state &&
1184 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1190 /* Now garbage collect the dead sprites.
1192 for (i = 0; i < ss->nsprites; i++)
1194 sprite *sp = ss->sprites[i];
1195 if (sp->state == DEAD)
1197 destroy_sprite (mi, sp);
1202 /* We can only ever end up with no sprites at all if the machine is
1203 being really slow and we hopped states directly from FULL to DEAD
1204 without passing OUT... */
1205 if (ss->nsprites == 0)
1208 if (!ss->redisplay_needed_p)
1211 if (debug_p && ss->now - ss->prev_frame_time > 1)
1212 fprintf (stderr, "%s: static screen for %.1f secs\n",
1213 blurb(), ss->now - ss->prev_frame_time);
1217 ss->fps = fps_compute (mi->fpst, 0);
1218 if (mi->fps_p) do_fps (mi);
1221 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1222 ss->prev_frame_time = ss->now;
1223 ss->redisplay_needed_p = False;
1227 XSCREENSAVER_MODULE_2 ("GLSlideshow", glslideshow, slideshow)