1 /* glslideshow, Copyright (c) 2003-2005 Jamie Zawinski <jwz@jwz.org>
2 * Loads a sequence of images and smoothly pans around them; crossfades
3 * when loading new images.
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 #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_LETTERBOX "True"
88 # define DEF_DEBUG "False"
89 # define DEF_MIPMAP "True"
91 #define DEFAULTS "*delay: 20000 \n" \
92 "*fadeDuration: " DEF_FADE_DURATION "\n" \
93 "*panDuration: " DEF_PAN_DURATION "\n" \
94 "*imageDuration: " DEF_IMAGE_DURATION "\n" \
95 "*zoom: " DEF_ZOOM "\n" \
96 "*titles: " DEF_TITLES "\n" \
97 "*FPScutoff: " DEF_FPS_CUTOFF "\n" \
98 "*letterbox: " DEF_LETTERBOX "\n" \
99 "*debug: " DEF_DEBUG "\n" \
100 "*mipmap: " DEF_MIPMAP "\n" \
101 "*wireframe: False \n" \
102 "*showFPS: False \n" \
103 "*fpsSolid: True \n" \
105 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
106 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
108 # include "xlockmore.h"
111 #define countof(x) (sizeof((x))/sizeof((*x)))
117 #include <sys/time.h>
120 #include "grab-ximage.h"
121 #include "glxfonts.h"
123 extern XtAppContext app;
131 int id; /* unique number for debugging */
132 char *title; /* the filename of this image */
133 int w, h; /* size in pixels of the image */
134 int tw, th; /* size in pixels of the texture */
135 XRectangle geom; /* where in the image the bits are */
136 Bool loaded_p; /* whether the image has finished loading */
137 Bool used_p; /* whether the image has yet appeared
139 GLuint texid; /* which texture contains the image */
140 int refcount; /* how many sprites refer to this image */
144 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
147 int id; /* unique number for debugging */
148 image *img; /* which image this animation displays */
149 GLfloat opacity; /* how to render it */
150 double start_time; /* when this animation began */
151 rect from, to, current; /* the journey this image is taking */
152 sprite_state state; /* the state we're in right now */
153 sprite_state prev_state; /* the state we were in previously */
154 double state_time; /* time of last state change */
155 int frame_count; /* frames since last state change */
160 GLXContext *glx_context;
161 int nimages; /* how many images are loaded or loading now */
162 image *images[10]; /* pointers to the images */
164 int nsprites; /* how many sprites are animating right now */
165 sprite *sprites[10]; /* pointers to the live sprites */
167 double now; /* current time in seconds */
168 double dawn_of_time; /* when the program launched */
169 double image_load_time; /* time when we last loaded a new image */
170 double prev_frame_time; /* time when we last drew a frame */
172 Bool redisplay_needed_p; /* Sometimes we can get away with not
173 re-painting. Tick this if a redisplay
175 Bool change_now_p; /* Set when the user clicks to ask for a new
178 GLfloat fps; /* approximate frame rate we're achieving */
179 GLfloat theoretical_fps; /* maximum frame rate that might be possible */
180 Bool checked_fps_p; /* Whether we have checked for a low
183 XFontStruct *xfont; /* for printing image file names */
186 int sprite_id, image_id; /* debugging id counters */
190 static slideshow_state *sss = NULL;
193 /* Command-line arguments
195 static int fade_seconds; /* Duration in seconds of fade transitions.
196 If 0, jump-cut instead of fading. */
197 static int pan_seconds; /* Duration of each pan through an image. */
198 static int image_seconds; /* How many seconds until loading a new image. */
199 static int zoom; /* How far in to zoom when panning, in percent of
200 image size: that is, 75 means "when zoomed all
201 the way in, 75% of the image will be visible."
203 static int fps_cutoff; /* If the frame-rate falls below this, turn off
205 static Bool letterbox_p; /* When a loaded image is not the same aspect
206 ratio as the window, whether to display black
209 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
210 static Bool do_titles; /* Display image titles. */
211 static Bool debug_p; /* Be loud and do weird things. */
214 static XrmOptionDescRec opts[] = {
215 {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
216 {"-pan", ".panDuration", XrmoptionSepArg, 0 },
217 {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
218 {"-zoom", ".zoom", XrmoptionSepArg, 0 },
219 {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
220 {"-titles", ".titles", XrmoptionNoArg, "True" },
221 {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
222 {"-clip", ".letterbox", XrmoptionNoArg, "False" },
223 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
224 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
225 {"-debug", ".debug", XrmoptionNoArg, "True" },
228 static argtype vars[] = {
229 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
230 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
231 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
232 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
233 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
234 { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
235 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
236 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
237 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
240 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
246 static char buf[255];
247 time_t now = time ((time_t *) 0);
248 char *ct = (char *) ctime (&now);
249 int n = strlen(progname);
251 strncpy(buf, progname, n);
254 strncpy(buf+n, ct+11, 8);
255 strcpy(buf+n+9, ": ");
260 /* Returns the current time in seconds as a double.
266 # ifdef GETTIMEOFDAY_TWO_ARGS
268 gettimeofday(&now, &tzp);
273 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
277 static void image_loaded_cb (const char *filename, XRectangle *geom,
278 int image_width, int image_height,
279 int texture_width, int texture_height,
283 /* Allocate an image structure and start a file loading in the background.
286 alloc_image (ModeInfo *mi)
288 slideshow_state *ss = &sss[MI_SCREEN(mi)];
289 int wire = MI_IS_WIREFRAME(mi);
290 image *img = (image *) calloc (1, sizeof (*img));
293 img->id = ++ss->image_id;
294 img->loaded_p = False;
298 glGenTextures (1, &img->texid);
299 if (img->texid <= 0) abort();
301 ss->image_load_time = ss->now;
304 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
306 screen_to_texture_async (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
307 img->texid, image_loaded_cb, img);
312 int iw=0, ih=0, tw=0, th=0;
313 glBindTexture (GL_TEXTURE_2D, img->texid);
315 if (! screen_to_texture (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
316 &filename, &geom, &iw, &ih, &tw, &th))
318 image_loaded_cb (filename, &geom, iw, ih, tw, th, img);
319 if (filename) free (filename);
322 ss->images[ss->nimages++] = img;
323 if (ss->nimages >= countof(ss->images)) abort();
329 /* Block until the first image is completely loaded.
330 We normally load images in the background, but we have nothing to draw
331 until we get that first image...
334 await_first_image (ModeInfo *mi)
336 slideshow_state *ss = &sss[MI_SCREEN(mi)];
339 if (ss->nimages != 0) abort();
340 img = alloc_image (mi);
342 while (! img->loaded_p)
344 usleep (100000); /* check every 1/10th sec */
345 if (i++ > 600) abort(); /* if a minute has passed, we're broken */
347 while (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
348 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
352 fprintf (stderr, "\n");
356 /* Callback that tells us that the texture has been loaded.
359 image_loaded_cb (const char *filename, XRectangle *geom,
360 int image_width, int image_height,
361 int texture_width, int texture_height,
364 image *img = (image *) closure;
365 ModeInfo *mi = img->mi;
366 /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
368 int wire = MI_IS_WIREFRAME(mi);
372 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
373 img->h = MI_HEIGHT (mi);
374 img->geom.width = img->w;
375 img->geom.height = img->h;
379 if (image_width == 0 || image_height == 0)
382 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
383 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
384 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
386 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
387 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
389 img->w = image_width;
390 img->h = image_height;
391 img->tw = texture_width;
392 img->th = texture_height;
394 img->title = (filename ? strdup (filename) : 0);
396 if (img->title) /* strip filename to part after last /. */
398 char *s = strrchr (img->title, '/');
399 if (s) strcpy (img->title, s+1);
403 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
404 blurb(), img->id, (img->title ? img->title : "(null)"));
407 img->loaded_p = True;
412 /* Free the image and texture, after nobody is referencing it.
415 destroy_image (ModeInfo *mi, image *img)
417 slideshow_state *ss = &sss[MI_SCREEN(mi)];
418 Bool freed_p = False;
422 if (!img->loaded_p) abort();
423 if (!img->used_p) abort();
424 if (img->texid <= 0) abort();
425 if (img->refcount != 0) abort();
427 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
428 if (ss->images[i] == img)
431 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
432 ss->images[j] = ss->images[j+1];
439 if (!freed_p) abort();
442 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
443 blurb(), img->id, (img->title ? img->title : "(null)"));
445 if (img->title) free (img->title);
446 glDeleteTextures (1, &img->texid);
451 /* Return an image to use for a sprite.
452 If it's time for a new one, get a new one.
453 Otherwise, use an old one.
454 Might return 0 if the machine is really slow.
457 get_image (ModeInfo *mi)
459 slideshow_state *ss = &sss[MI_SCREEN(mi)];
461 double now = ss->now;
462 Bool want_new_p = (ss->change_now_p ||
463 ss->image_load_time + image_seconds <= now);
466 image *loading_img = 0;
469 for (i = 0; i < ss->nimages; i++)
471 image *img2 = ss->images[i];
475 else if (!img2->used_p)
481 if (want_new_p && new_img)
482 img = new_img, new_img = 0, ss->change_now_p = False;
484 img = old_img, old_img = 0;
486 img = new_img, new_img = 0, ss->change_now_p = False;
488 /* Make sure that there is always one unused image in the pipe.
490 if (!new_img && !loading_img)
497 /* Pick random starting and ending positions for the given sprite.
500 randomize_sprite (ModeInfo *mi, sprite *sp)
502 int vp_w = MI_WIDTH(mi);
503 int vp_h = MI_HEIGHT(mi);
504 int img_w = sp->img->geom.width;
505 int img_h = sp->img->geom.height;
506 int min_w, min_h, max_w, max_h;
507 double ratio = (double) img_h / img_w;
519 min_h = img_h * (float) vp_w / img_w;
523 min_w = img_w * (float) vp_h / img_h;
528 max_w = min_w * 100 / zoom;
529 max_h = min_h * 100 / zoom;
531 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
532 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
533 sp->from.h = sp->from.w * ratio;
534 sp->to.h = sp->to.w * ratio;
536 if (zoom == 100) /* only one box, and it is centered */
538 sp->from.x = (sp->from.w > vp_w
539 ? -(sp->from.w - vp_w) / 2
540 : (vp_w - sp->from.w) / 2);
541 sp->from.y = (sp->from.h > vp_h
542 ? -(sp->from.h - vp_h) / 2
543 : (vp_h - sp->from.h) / 2);
546 else /* position both boxes randomly */
548 sp->from.x = (sp->from.w > vp_w
549 ? -frand (sp->from.w - vp_w)
550 : frand (vp_w - sp->from.w));
551 sp->from.y = (sp->from.h > vp_h
552 ? -frand (sp->from.h - vp_h)
553 : frand (vp_h - sp->from.h));
554 sp->to.x = (sp->to.w > vp_w
555 ? -frand (sp->to.w - vp_w)
556 : frand (vp_w - sp->to.w));
557 sp->to.y = (sp->to.h > vp_h
558 ? -frand (sp->to.h - vp_h)
559 : frand (vp_h - sp->to.h));
569 /* Make sure the aspect ratios are within 0.0001 of each other.
571 if ((int) (0.5 + (sp->from.w * 1000 / sp->from.h)) !=
572 (int) (0.5 + (sp->to.w * 1000 / sp->to.h)))
586 /* Allocate a new sprite and start its animation going.
589 new_sprite (ModeInfo *mi)
591 slideshow_state *ss = &sss[MI_SCREEN(mi)];
592 image *img = get_image (mi);
597 /* Oops, no images yet! The machine is probably hurting bad.
598 Let's give it some time before thrashing again. */
603 sp = (sprite *) calloc (1, sizeof (*sp));
604 sp->id = ++ss->sprite_id;
605 sp->start_time = ss->now;
606 sp->state_time = sp->start_time;
607 sp->state = sp->prev_state = NEW;
611 sp->img->used_p = True;
613 ss->sprites[ss->nsprites++] = sp;
614 if (ss->nsprites >= countof(ss->sprites)) abort();
616 randomize_sprite (mi, sp);
622 /* Free the given sprite, and decrement the reference count on its image.
625 destroy_sprite (ModeInfo *mi, sprite *sp)
627 slideshow_state *ss = &sss[MI_SCREEN(mi)];
628 Bool freed_p = False;
633 if (sp->state != DEAD) abort();
636 if (!img->loaded_p) abort();
637 if (!img->used_p) abort();
638 if (img->refcount <= 0) abort();
640 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
641 if (ss->sprites[i] == sp)
644 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
645 ss->sprites[j] = ss->sprites[j+1];
652 if (!freed_p) abort();
657 if (img->refcount < 0) abort();
658 if (img->refcount == 0)
659 destroy_image (mi, img);
663 /* Updates the sprite for the current frame of the animation based on
664 its creation time compared to the current wall clock.
667 tick_sprite (ModeInfo *mi, sprite *sp)
669 slideshow_state *ss = &sss[MI_SCREEN(mi)];
670 image *img = sp->img;
671 double now = ss->now;
674 rect prev_rect = sp->current;
675 GLfloat prev_opacity = sp->opacity;
677 if (! sp->img) abort();
678 if (! img->loaded_p) abort();
680 secs = now - sp->start_time;
681 ratio = secs / (pan_seconds + fade_seconds);
682 if (ratio > 1) ratio = 1;
684 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
685 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
686 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
687 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
689 sp->prev_state = sp->state;
691 if (secs < fade_seconds)
694 sp->opacity = secs / (GLfloat) fade_seconds;
696 else if (secs < pan_seconds)
701 else if (secs < pan_seconds + fade_seconds)
704 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
712 if (sp->state != sp->prev_state &&
713 (sp->prev_state == IN ||
714 sp->prev_state == FULL))
716 double secs = now - sp->state_time;
720 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
722 (sp->prev_state == IN ? "fade" : "pan "),
725 sp->frame_count / secs,
726 ss->theoretical_fps);
728 sp->state_time = now;
734 if (sp->state != DEAD &&
735 (prev_rect.x != sp->current.x ||
736 prev_rect.y != sp->current.y ||
737 prev_rect.w != sp->current.w ||
738 prev_rect.h != sp->current.h ||
739 prev_opacity != sp->opacity))
740 ss->redisplay_needed_p = True;
744 /* Draw the given sprite at the phase of its animation dictated by
745 its creation time compared to the current wall clock.
748 draw_sprite (ModeInfo *mi, sprite *sp)
750 slideshow_state *ss = &sss[MI_SCREEN(mi)];
751 int wire = MI_IS_WIREFRAME(mi);
752 image *img = sp->img;
754 if (! sp->img) abort();
755 if (! img->loaded_p) abort();
759 glTranslatef (sp->current.x, sp->current.y, 0);
760 glScalef (sp->current.w, sp->current.h, 1);
762 if (wire) /* Draw a grid inside the box */
765 GLfloat dx = dy * img->w / img->h;
769 glColor4f (sp->opacity, 0, 0, 1);
771 glColor4f (0, 0, sp->opacity, 1);
774 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
775 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
777 for (y = 0; y < 1+dy; y += dy)
779 GLfloat yy = (y > 1 ? 1 : y);
780 for (x = 0.5; x < 1+dx; x += dx)
782 GLfloat xx = (x > 1 ? 1 : x);
783 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
784 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
786 for (x = 0.5; x > -dx; x -= dx)
788 GLfloat xx = (x < 0 ? 0 : x);
789 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
790 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
795 else /* Draw the texture quad */
797 GLfloat texw = img->geom.width / (GLfloat) img->tw;
798 GLfloat texh = img->geom.height / (GLfloat) img->th;
799 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
800 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
801 GLfloat texx2 = texx1 + texw;
802 GLfloat texy2 = texy1 + texh;
804 glBindTexture (GL_TEXTURE_2D, img->texid);
805 glColor4f (1, 1, 1, sp->opacity);
806 glNormal3f (0, 0, 1);
808 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
809 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
810 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
811 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
814 if (debug_p) /* Draw a border around the image */
816 if (!wire) glDisable (GL_TEXTURE_2D);
819 glColor4f (sp->opacity, 0, 0, 1);
821 glColor4f (0, 0, sp->opacity, 1);
823 glBegin (GL_LINE_LOOP);
824 glVertex3f (0, 0, 0);
825 glVertex3f (0, 1, 0);
826 glVertex3f (1, 1, 0);
827 glVertex3f (1, 0, 0);
830 if (!wire) glEnable (GL_TEXTURE_2D);
836 img->title && *img->title)
839 int y = mi->xgwa.height - 10;
840 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
841 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
842 mi->xgwa.width, mi->xgwa.height, x, y,
845 glColor4f (1, 1, 1, sp->opacity);
846 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
847 mi->xgwa.width, mi->xgwa.height, x, y,
855 if (!wire) glDisable (GL_TEXTURE_2D);
858 glColor4f (1, 0, 0, 1);
860 glColor4f (0, 0, 1, 1);
862 /* Draw the "from" and "to" boxes
864 glBegin (GL_LINE_LOOP);
865 glVertex3f (sp->from.x, sp->from.y, 0);
866 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
867 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
868 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
871 glBegin (GL_LINE_LOOP);
872 glVertex3f (sp->to.x, sp->to.y, 0);
873 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
874 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
875 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
878 if (!wire) glEnable (GL_TEXTURE_2D);
884 tick_sprites (ModeInfo *mi)
886 slideshow_state *ss = &sss[MI_SCREEN(mi)];
888 for (i = 0; i < ss->nsprites; i++)
889 tick_sprite (mi, ss->sprites[i]);
894 draw_sprites (ModeInfo *mi)
896 slideshow_state *ss = &sss[MI_SCREEN(mi)];
899 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
902 for (i = 0; i < ss->nsprites; i++)
903 draw_sprite (mi, ss->sprites[i]);
906 if (debug_p) /* draw a white box (the "screen") */
908 int wire = MI_IS_WIREFRAME(mi);
910 if (!wire) glDisable (GL_TEXTURE_2D);
912 glColor4f (1, 1, 1, 1);
913 glBegin (GL_LINE_LOOP);
914 glVertex3f (0, 0, 0);
915 glVertex3f (0, 1, 0);
916 glVertex3f (1, 1, 0);
917 glVertex3f (1, 0, 0);
920 if (!wire) glEnable (GL_TEXTURE_2D);
926 reshape_slideshow (ModeInfo *mi, int width, int height)
928 slideshow_state *ss = &sss[MI_SCREEN(mi)];
930 glViewport (0, 0, width, height);
931 glMatrixMode (GL_PROJECTION);
933 glMatrixMode (GL_MODELVIEW);
940 s *= (zoom / 100.0) * 0.75;
941 if (s < 0.1) s = 0.1;
945 glTranslatef (-0.5, -0.5, 0);
947 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
949 ss->redisplay_needed_p = True;
954 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
956 slideshow_state *ss = &sss[MI_SCREEN(mi)];
958 if (event->xany.type == ButtonPress &&
959 event->xbutton.button == Button1)
961 ss->change_now_p = True;
964 else if (event->xany.type == KeyPress)
968 XLookupString (&event->xkey, &c, 1, &keysym, 0);
969 if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
971 ss->change_now_p = True;
975 else if (event->xany.type == Expose ||
976 event->xany.type == GraphicsExpose ||
977 event->xany.type == VisibilityNotify)
979 ss->redisplay_needed_p = True;
981 fprintf (stderr, "%s: exposure\n", blurb());
989 /* Do some sanity checking on various user-supplied values, and make
990 sure they are all internally consistent.
993 sanity_check (ModeInfo *mi)
995 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
996 else if (zoom > 100) zoom = 100;
998 if (zoom == 100) /* with no zooming, there is no panning */
1001 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
1002 pan_seconds = fade_seconds;
1004 if (pan_seconds == 0) /* no zero-length cycles, please... */
1007 if (image_seconds < pan_seconds) /* we only change images at fade-time */
1008 image_seconds = pan_seconds;
1010 /* If we're not panning/zooming within the image, then there's no point
1011 in crossfading the image with itself -- only do crossfades when changing
1013 if (zoom == 100 && pan_seconds < image_seconds)
1014 pan_seconds = image_seconds;
1016 /* No need to use mipmaps if we're not changing the image size much */
1017 if (zoom >= 80) mipmap_p = False;
1019 if (fps_cutoff < 0) fps_cutoff = 0;
1020 else if (fps_cutoff > 30) fps_cutoff = 30;
1025 check_fps (ModeInfo *mi)
1027 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1029 static double time_elapsed = 0;
1030 static int frames_elapsed = 0;
1031 double start_time, end_time, wall_elapsed, frame_duration, fps;
1034 start_time = ss->now;
1035 end_time = double_time();
1036 frame_duration = end_time - start_time; /* time spent drawing this frame */
1037 time_elapsed += frame_duration; /* time spent drawing all frames */
1040 wall_elapsed = end_time - ss->dawn_of_time;
1041 fps = frames_elapsed / time_elapsed;
1042 ss->theoretical_fps = fps;
1044 if (ss->checked_fps_p) return;
1046 if (wall_elapsed <= 8) /* too early to be sure */
1049 ss->checked_fps_p = True;
1051 if (fps >= fps_cutoff)
1055 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1056 blurb(), fps, frames_elapsed, wall_elapsed);
1061 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1068 for (i = 0; i < ss->nsprites; i++)
1070 sprite *sp = ss->sprites[i];
1071 randomize_sprite (mi, sp);
1075 ss->redisplay_needed_p = True;
1077 /* Need this in case zoom changed. */
1078 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1082 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1085 hack_resources (void)
1088 char *res = "desktopGrabber";
1089 char *val = get_string_resource (res, "DesktopGrabber");
1093 sprintf (buf1, "%.100s.%.100s", progclass, res);
1094 sprintf (buf2, "%.200s -v", val);
1096 value.size = strlen(buf2);
1097 XrmPutResource (&db, buf1, "String", &value);
1103 init_slideshow (ModeInfo *mi)
1105 int screen = MI_SCREEN(mi);
1106 slideshow_state *ss;
1107 int wire = MI_IS_WIREFRAME(mi);
1110 if ((sss = (slideshow_state *)
1111 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1116 if ((ss->glx_context = init_GL(mi)) != NULL) {
1117 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1123 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1124 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1129 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1130 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1132 glDisable (GL_LIGHTING);
1133 glDisable (GL_DEPTH_TEST);
1134 glDepthMask (GL_FALSE);
1135 glEnable (GL_CULL_FACE);
1136 glCullFace (GL_BACK);
1140 glEnable (GL_TEXTURE_2D);
1141 glShadeModel (GL_SMOOTH);
1142 glEnable (GL_BLEND);
1143 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1146 if (debug_p) glLineWidth (3);
1148 load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1153 ss->now = double_time();
1154 ss->dawn_of_time = ss->now;
1155 ss->prev_frame_time = ss->now;
1157 await_first_image (mi); /* wait for first image to fully load */
1159 ss->now = double_time();
1160 ss->dawn_of_time = ss->now;
1162 new_sprite (mi); /* start first sprite fading in */
1167 draw_slideshow (ModeInfo *mi)
1169 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1172 if (!ss->glx_context)
1175 ss->now = double_time();
1177 /* Each sprite has three states: fading in, full, fading out.
1178 The in/out states overlap like this:
1180 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1181 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1182 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1184 So as soon as a sprite goes into the "out" state, we create
1185 a new sprite (in the "in" state.)
1188 if (ss->nsprites > 2) abort();
1190 /* If a sprite is just entering the fade-out state,
1191 then add a new sprite in the fade-in state.
1193 for (i = 0; i < ss->nsprites; i++)
1195 sprite *sp = ss->sprites[i];
1196 if (sp->state != sp->prev_state &&
1197 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1203 /* Now garbage collect the dead sprites.
1205 for (i = 0; i < ss->nsprites; i++)
1207 sprite *sp = ss->sprites[i];
1208 if (sp->state == DEAD)
1210 destroy_sprite (mi, sp);
1215 /* We can only ever end up with no sprites at all if the machine is
1216 being really slow and we hopped states directly from FULL to DEAD
1217 without passing OUT... */
1218 if (ss->nsprites == 0)
1221 if (!ss->redisplay_needed_p)
1224 if (debug_p && ss->now - ss->prev_frame_time > 1)
1225 fprintf (stderr, "%s: static screen for %.1f secs\n",
1226 blurb(), ss->now - ss->prev_frame_time);
1230 ss->fps = fps_1 (mi);
1231 if (mi->fps_p) fps_2 (mi);
1234 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1235 ss->prev_frame_time = ss->now;
1236 ss->redisplay_needed_p = False;