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 "*wireframe: False \n" \
93 "*showFPS: False \n" \
94 "*fpsSolid: True \n" \
96 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
97 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
99 # include "xlockmore.h"
102 #define countof(x) (sizeof((x))/sizeof((*x)))
108 #include <sys/time.h>
111 #include "grab-ximage.h"
112 #include "glxfonts.h"
114 extern XtAppContext app;
122 int id; /* unique number for debugging */
123 char *title; /* the filename of this image */
124 int w, h; /* size in pixels of the image */
125 int tw, th; /* size in pixels of the texture */
126 XRectangle geom; /* where in the image the bits are */
127 Bool loaded_p; /* whether the image has finished loading */
128 Bool used_p; /* whether the image has yet appeared
130 GLuint texid; /* which texture contains the image */
131 int refcount; /* how many sprites refer to this image */
135 typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
138 int id; /* unique number for debugging */
139 image *img; /* which image this animation displays */
140 GLfloat opacity; /* how to render it */
141 double start_time; /* when this animation began */
142 rect from, to, current; /* the journey this image is taking */
143 sprite_state state; /* the state we're in right now */
144 sprite_state prev_state; /* the state we were in previously */
145 double state_time; /* time of last state change */
146 int frame_count; /* frames since last state change */
151 GLXContext *glx_context;
152 int nimages; /* how many images are loaded or loading now */
153 image *images[10]; /* pointers to the images */
155 int nsprites; /* how many sprites are animating right now */
156 sprite *sprites[10]; /* pointers to the live sprites */
158 double now; /* current time in seconds */
159 double dawn_of_time; /* when the program launched */
160 double image_load_time; /* time when we last loaded a new image */
161 double prev_frame_time; /* time when we last drew a frame */
163 Bool redisplay_needed_p; /* Sometimes we can get away with not
164 re-painting. Tick this if a redisplay
166 Bool change_now_p; /* Set when the user clicks to ask for a new
169 GLfloat fps; /* approximate frame rate we're achieving */
170 GLfloat theoretical_fps; /* maximum frame rate that might be possible */
171 Bool checked_fps_p; /* Whether we have checked for a low
174 XFontStruct *xfont; /* for printing image file names */
177 int sprite_id, image_id; /* debugging id counters */
181 static slideshow_state *sss = NULL;
184 /* Command-line arguments
186 static int fade_seconds; /* Duration in seconds of fade transitions.
187 If 0, jump-cut instead of fading. */
188 static int pan_seconds; /* Duration of each pan through an image. */
189 static int image_seconds; /* How many seconds until loading a new image. */
190 static int zoom; /* How far in to zoom when panning, in percent of
191 image size: that is, 75 means "when zoomed all
192 the way in, 75% of the image will be visible."
194 static int fps_cutoff; /* If the frame-rate falls below this, turn off
196 static Bool letterbox_p; /* When a loaded image is not the same aspect
197 ratio as the window, whether to display black
200 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
201 static Bool do_titles; /* Display image titles. */
202 static Bool debug_p; /* Be loud and do weird things. */
205 static XrmOptionDescRec opts[] = {
206 {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
207 {"-pan", ".panDuration", XrmoptionSepArg, 0 },
208 {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
209 {"-zoom", ".zoom", XrmoptionSepArg, 0 },
210 {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
211 {"-titles", ".titles", XrmoptionNoArg, "True" },
212 {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
213 {"-clip", ".letterbox", XrmoptionNoArg, "False" },
214 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
215 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
216 {"-debug", ".debug", XrmoptionNoArg, "True" },
219 static argtype vars[] = {
220 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
221 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
222 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
223 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
224 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
225 { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
226 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
227 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
228 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
231 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
237 static char buf[255];
238 time_t now = time ((time_t *) 0);
239 char *ct = (char *) ctime (&now);
240 int n = strlen(progname);
242 strncpy(buf, progname, n);
245 strncpy(buf+n, ct+11, 8);
246 strcpy(buf+n+9, ": ");
251 /* Returns the current time in seconds as a double.
257 # ifdef GETTIMEOFDAY_TWO_ARGS
259 gettimeofday(&now, &tzp);
264 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
268 static void image_loaded_cb (const char *filename, XRectangle *geom,
269 int image_width, int image_height,
270 int texture_width, int texture_height,
274 /* Allocate an image structure and start a file loading in the background.
277 alloc_image (ModeInfo *mi)
279 slideshow_state *ss = &sss[MI_SCREEN(mi)];
280 int wire = MI_IS_WIREFRAME(mi);
281 image *img = (image *) calloc (1, sizeof (*img));
284 img->id = ++ss->image_id;
285 img->loaded_p = False;
289 glGenTextures (1, &img->texid);
290 if (img->texid <= 0) abort();
292 ss->image_load_time = ss->now;
295 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
297 screen_to_texture_async (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
298 img->texid, image_loaded_cb, img);
303 int iw=0, ih=0, tw=0, th=0;
304 glBindTexture (GL_TEXTURE_2D, img->texid);
306 if (! screen_to_texture (mi->xgwa.screen, mi->window, 0, 0, mipmap_p,
307 &filename, &geom, &iw, &ih, &tw, &th))
309 image_loaded_cb (filename, &geom, iw, ih, tw, th, img);
310 if (filename) free (filename);
313 ss->images[ss->nimages++] = img;
314 if (ss->nimages >= countof(ss->images)) abort();
320 /* Block until the first image is completely loaded.
321 We normally load images in the background, but we have nothing to draw
322 until we get that first image...
325 await_first_image (ModeInfo *mi)
327 slideshow_state *ss = &sss[MI_SCREEN(mi)];
330 if (ss->nimages != 0) abort();
331 img = alloc_image (mi);
333 while (! img->loaded_p)
335 usleep (100000); /* check every 1/10th sec */
336 if (i++ > 600) abort(); /* if a minute has passed, we're broken */
338 while (XtAppPending (app) & (XtIMTimer|XtIMAlternateInput))
339 XtAppProcessEvent (app, XtIMTimer|XtIMAlternateInput);
343 fprintf (stderr, "\n");
347 /* Callback that tells us that the texture has been loaded.
350 image_loaded_cb (const char *filename, XRectangle *geom,
351 int image_width, int image_height,
352 int texture_width, int texture_height,
355 image *img = (image *) closure;
356 ModeInfo *mi = img->mi;
357 /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
359 int wire = MI_IS_WIREFRAME(mi);
363 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
364 img->h = MI_HEIGHT (mi);
365 img->geom.width = img->w;
366 img->geom.height = img->h;
370 if (image_width == 0 || image_height == 0)
373 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
374 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
375 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
377 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
378 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
380 img->w = image_width;
381 img->h = image_height;
382 img->tw = texture_width;
383 img->th = texture_height;
385 img->title = (filename ? strdup (filename) : 0);
387 /* If the image's width doesn't come back as the width of the screen,
388 then the image must have been scaled down (due to insufficient
389 texture memory.) Scale up the coordinates to stretch the image
392 if (img->w != MI_WIDTH(mi))
394 double scale = (double) MI_WIDTH(mi) / img->w;
399 img->geom.x *= scale;
400 img->geom.y *= scale;
401 img->geom.width *= scale;
402 img->geom.height *= scale;
405 if (img->title) /* strip filename to part after last /. */
407 char *s = strrchr (img->title, '/');
408 if (s) strcpy (img->title, s+1);
412 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
413 blurb(), img->id, (img->title ? img->title : "(null)"));
416 img->loaded_p = True;
421 /* Free the image and texture, after nobody is referencing it.
424 destroy_image (ModeInfo *mi, image *img)
426 slideshow_state *ss = &sss[MI_SCREEN(mi)];
427 Bool freed_p = False;
431 if (!img->loaded_p) abort();
432 if (!img->used_p) abort();
433 if (img->texid <= 0) abort();
434 if (img->refcount != 0) abort();
436 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
437 if (ss->images[i] == img)
440 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
441 ss->images[j] = ss->images[j+1];
448 if (!freed_p) abort();
451 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
452 blurb(), img->id, (img->title ? img->title : "(null)"));
454 if (img->title) free (img->title);
455 glDeleteTextures (1, &img->texid);
460 /* Return an image to use for a sprite.
461 If it's time for a new one, get a new one.
462 Otherwise, use an old one.
463 Might return 0 if the machine is really slow.
466 get_image (ModeInfo *mi)
468 slideshow_state *ss = &sss[MI_SCREEN(mi)];
470 double now = ss->now;
471 Bool want_new_p = (ss->change_now_p ||
472 ss->image_load_time + image_seconds <= now);
475 image *loading_img = 0;
478 for (i = 0; i < ss->nimages; i++)
480 image *img2 = ss->images[i];
484 else if (!img2->used_p)
490 if (want_new_p && new_img)
491 img = new_img, new_img = 0, ss->change_now_p = False;
493 img = old_img, old_img = 0;
495 img = new_img, new_img = 0, ss->change_now_p = False;
497 /* Make sure that there is always one unused image in the pipe.
499 if (!new_img && !loading_img)
506 /* Pick random starting and ending positions for the given sprite.
509 randomize_sprite (ModeInfo *mi, sprite *sp)
511 int vp_w = MI_WIDTH(mi);
512 int vp_h = MI_HEIGHT(mi);
513 int img_w = sp->img->geom.width;
514 int img_h = sp->img->geom.height;
515 int min_w, min_h, max_w, max_h;
516 double ratio = (double) img_h / img_w;
528 min_h = img_h * (float) vp_w / img_w;
532 min_w = img_w * (float) vp_h / img_h;
537 max_w = min_w * 100 / zoom;
538 max_h = min_h * 100 / zoom;
540 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
541 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
542 sp->from.h = sp->from.w * ratio;
543 sp->to.h = sp->to.w * ratio;
545 if (zoom == 100) /* only one box, and it is centered */
547 sp->from.x = (sp->from.w > vp_w
548 ? -(sp->from.w - vp_w) / 2
549 : (vp_w - sp->from.w) / 2);
550 sp->from.y = (sp->from.h > vp_h
551 ? -(sp->from.h - vp_h) / 2
552 : (vp_h - sp->from.h) / 2);
555 else /* position both boxes randomly */
557 sp->from.x = (sp->from.w > vp_w
558 ? -frand (sp->from.w - vp_w)
559 : frand (vp_w - sp->from.w));
560 sp->from.y = (sp->from.h > vp_h
561 ? -frand (sp->from.h - vp_h)
562 : frand (vp_h - sp->from.h));
563 sp->to.x = (sp->to.w > vp_w
564 ? -frand (sp->to.w - vp_w)
565 : frand (vp_w - sp->to.w));
566 sp->to.y = (sp->to.h > vp_h
567 ? -frand (sp->to.h - vp_h)
568 : frand (vp_h - sp->to.h));
578 /* Make sure the aspect ratios are within 0.0001 of each other.
580 if ((int) (0.5 + (sp->from.w * 1000 / sp->from.h)) !=
581 (int) (0.5 + (sp->to.w * 1000 / sp->to.h)))
595 /* Allocate a new sprite and start its animation going.
598 new_sprite (ModeInfo *mi)
600 slideshow_state *ss = &sss[MI_SCREEN(mi)];
601 image *img = get_image (mi);
606 /* Oops, no images yet! The machine is probably hurting bad.
607 Let's give it some time before thrashing again. */
612 sp = (sprite *) calloc (1, sizeof (*sp));
613 sp->id = ++ss->sprite_id;
614 sp->start_time = ss->now;
615 sp->state_time = sp->start_time;
616 sp->state = sp->prev_state = NEW;
620 sp->img->used_p = True;
622 ss->sprites[ss->nsprites++] = sp;
623 if (ss->nsprites >= countof(ss->sprites)) abort();
625 randomize_sprite (mi, sp);
631 /* Free the given sprite, and decrement the reference count on its image.
634 destroy_sprite (ModeInfo *mi, sprite *sp)
636 slideshow_state *ss = &sss[MI_SCREEN(mi)];
637 Bool freed_p = False;
642 if (sp->state != DEAD) abort();
645 if (!img->loaded_p) abort();
646 if (!img->used_p) abort();
647 if (img->refcount <= 0) abort();
649 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
650 if (ss->sprites[i] == sp)
653 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
654 ss->sprites[j] = ss->sprites[j+1];
661 if (!freed_p) abort();
666 if (img->refcount < 0) abort();
667 if (img->refcount == 0)
668 destroy_image (mi, img);
672 /* Updates the sprite for the current frame of the animation based on
673 its creation time compared to the current wall clock.
676 tick_sprite (ModeInfo *mi, sprite *sp)
678 slideshow_state *ss = &sss[MI_SCREEN(mi)];
679 image *img = sp->img;
680 double now = ss->now;
683 rect prev_rect = sp->current;
684 GLfloat prev_opacity = sp->opacity;
686 if (! sp->img) abort();
687 if (! img->loaded_p) abort();
689 secs = now - sp->start_time;
690 ratio = secs / (pan_seconds + fade_seconds);
691 if (ratio > 1) ratio = 1;
693 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
694 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
695 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
696 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
698 sp->prev_state = sp->state;
700 if (secs < fade_seconds)
703 sp->opacity = secs / (GLfloat) fade_seconds;
705 else if (secs < pan_seconds)
710 else if (secs < pan_seconds + fade_seconds)
713 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
721 if (sp->state != sp->prev_state &&
722 (sp->prev_state == IN ||
723 sp->prev_state == FULL))
725 double secs = now - sp->state_time;
729 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
731 (sp->prev_state == IN ? "fade" : "pan "),
734 sp->frame_count / secs,
735 ss->theoretical_fps);
737 sp->state_time = now;
743 if (sp->state != DEAD &&
744 (prev_rect.x != sp->current.x ||
745 prev_rect.y != sp->current.y ||
746 prev_rect.w != sp->current.w ||
747 prev_rect.h != sp->current.h ||
748 prev_opacity != sp->opacity))
749 ss->redisplay_needed_p = True;
753 /* Draw the given sprite at the phase of its animation dictated by
754 its creation time compared to the current wall clock.
757 draw_sprite (ModeInfo *mi, sprite *sp)
759 slideshow_state *ss = &sss[MI_SCREEN(mi)];
760 int wire = MI_IS_WIREFRAME(mi);
761 image *img = sp->img;
763 if (! sp->img) abort();
764 if (! img->loaded_p) abort();
768 glTranslatef (sp->current.x, sp->current.y, 0);
769 glScalef (sp->current.w, sp->current.h, 1);
771 if (wire) /* Draw a grid inside the box */
774 GLfloat dx = dy * img->w / img->h;
778 glColor4f (sp->opacity, 0, 0, 1);
780 glColor4f (0, 0, sp->opacity, 1);
783 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
784 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
786 for (y = 0; y < 1+dy; y += dy)
788 GLfloat yy = (y > 1 ? 1 : y);
789 for (x = 0.5; x < 1+dx; x += dx)
791 GLfloat xx = (x > 1 ? 1 : x);
792 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
793 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
795 for (x = 0.5; x > -dx; x -= dx)
797 GLfloat xx = (x < 0 ? 0 : x);
798 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
799 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
804 else /* Draw the texture quad */
806 GLfloat texw = img->geom.width / (GLfloat) img->tw;
807 GLfloat texh = img->geom.height / (GLfloat) img->th;
808 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
809 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
810 GLfloat texx2 = texx1 + texw;
811 GLfloat texy2 = texy1 + texh;
813 glBindTexture (GL_TEXTURE_2D, img->texid);
814 glColor4f (1, 1, 1, sp->opacity);
815 glNormal3f (0, 0, 1);
817 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
818 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
819 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
820 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
823 if (debug_p) /* Draw a border around the image */
825 if (!wire) glDisable (GL_TEXTURE_2D);
828 glColor4f (sp->opacity, 0, 0, 1);
830 glColor4f (0, 0, sp->opacity, 1);
832 glBegin (GL_LINE_LOOP);
833 glVertex3f (0, 0, 0);
834 glVertex3f (0, 1, 0);
835 glVertex3f (1, 1, 0);
836 glVertex3f (1, 0, 0);
839 if (!wire) glEnable (GL_TEXTURE_2D);
845 img->title && *img->title)
848 int y = mi->xgwa.height - 10;
849 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
850 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
851 mi->xgwa.width, mi->xgwa.height, x, y,
854 glColor4f (1, 1, 1, sp->opacity);
855 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
856 mi->xgwa.width, mi->xgwa.height, x, y,
864 if (!wire) glDisable (GL_TEXTURE_2D);
867 glColor4f (1, 0, 0, 1);
869 glColor4f (0, 0, 1, 1);
871 /* Draw the "from" and "to" boxes
873 glBegin (GL_LINE_LOOP);
874 glVertex3f (sp->from.x, sp->from.y, 0);
875 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
876 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
877 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
880 glBegin (GL_LINE_LOOP);
881 glVertex3f (sp->to.x, sp->to.y, 0);
882 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
883 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
884 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
887 if (!wire) glEnable (GL_TEXTURE_2D);
893 tick_sprites (ModeInfo *mi)
895 slideshow_state *ss = &sss[MI_SCREEN(mi)];
897 for (i = 0; i < ss->nsprites; i++)
898 tick_sprite (mi, ss->sprites[i]);
903 draw_sprites (ModeInfo *mi)
905 slideshow_state *ss = &sss[MI_SCREEN(mi)];
908 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
911 for (i = 0; i < ss->nsprites; i++)
912 draw_sprite (mi, ss->sprites[i]);
915 if (debug_p) /* draw a white box (the "screen") */
917 int wire = MI_IS_WIREFRAME(mi);
919 if (!wire) glDisable (GL_TEXTURE_2D);
921 glColor4f (1, 1, 1, 1);
922 glBegin (GL_LINE_LOOP);
923 glVertex3f (0, 0, 0);
924 glVertex3f (0, 1, 0);
925 glVertex3f (1, 1, 0);
926 glVertex3f (1, 0, 0);
929 if (!wire) glEnable (GL_TEXTURE_2D);
935 reshape_slideshow (ModeInfo *mi, int width, int height)
937 slideshow_state *ss = &sss[MI_SCREEN(mi)];
939 glViewport (0, 0, width, height);
940 glMatrixMode (GL_PROJECTION);
942 glMatrixMode (GL_MODELVIEW);
949 s *= (zoom / 100.0) * 0.75;
950 if (s < 0.1) s = 0.1;
954 glTranslatef (-0.5, -0.5, 0);
956 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
958 ss->redisplay_needed_p = True;
963 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
965 slideshow_state *ss = &sss[MI_SCREEN(mi)];
967 if (event->xany.type == ButtonPress &&
968 event->xbutton.button == Button1)
970 ss->change_now_p = True;
973 else if (event->xany.type == KeyPress)
977 XLookupString (&event->xkey, &c, 1, &keysym, 0);
978 if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
980 ss->change_now_p = True;
984 else if (event->xany.type == Expose ||
985 event->xany.type == GraphicsExpose ||
986 event->xany.type == VisibilityNotify)
988 ss->redisplay_needed_p = True;
990 fprintf (stderr, "%s: exposure\n", blurb());
998 /* Do some sanity checking on various user-supplied values, and make
999 sure they are all internally consistent.
1002 sanity_check (ModeInfo *mi)
1004 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
1005 else if (zoom > 100) zoom = 100;
1007 if (zoom == 100) /* with no zooming, there is no panning */
1010 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
1011 pan_seconds = fade_seconds;
1013 if (pan_seconds == 0) /* no zero-length cycles, please... */
1016 if (image_seconds < pan_seconds) /* we only change images at fade-time */
1017 image_seconds = pan_seconds;
1019 /* If we're not panning/zooming within the image, then there's no point
1020 in crossfading the image with itself -- only do crossfades when changing
1022 if (zoom == 100 && pan_seconds < image_seconds)
1023 pan_seconds = image_seconds;
1025 /* No need to use mipmaps if we're not changing the image size much */
1026 if (zoom >= 80) mipmap_p = False;
1028 if (fps_cutoff < 0) fps_cutoff = 0;
1029 else if (fps_cutoff > 30) fps_cutoff = 30;
1034 check_fps (ModeInfo *mi)
1036 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1038 static double time_elapsed = 0;
1039 static int frames_elapsed = 0;
1040 double start_time, end_time, wall_elapsed, frame_duration, fps;
1043 start_time = ss->now;
1044 end_time = double_time();
1045 frame_duration = end_time - start_time; /* time spent drawing this frame */
1046 time_elapsed += frame_duration; /* time spent drawing all frames */
1049 wall_elapsed = end_time - ss->dawn_of_time;
1050 fps = frames_elapsed / time_elapsed;
1051 ss->theoretical_fps = fps;
1053 if (ss->checked_fps_p) return;
1055 if (wall_elapsed <= 8) /* too early to be sure */
1058 ss->checked_fps_p = True;
1060 if (fps >= fps_cutoff)
1064 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1065 blurb(), fps, frames_elapsed, wall_elapsed);
1070 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1077 for (i = 0; i < ss->nsprites; i++)
1079 sprite *sp = ss->sprites[i];
1080 randomize_sprite (mi, sp);
1084 ss->redisplay_needed_p = True;
1086 /* Need this in case zoom changed. */
1087 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1091 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1094 hack_resources (void)
1097 char *res = "desktopGrabber";
1098 char *val = get_string_resource (res, "DesktopGrabber");
1102 sprintf (buf1, "%.100s.%.100s", progclass, res);
1103 sprintf (buf2, "%.200s -v", val);
1105 value.size = strlen(buf2);
1106 XrmPutResource (&db, buf1, "String", &value);
1112 init_slideshow (ModeInfo *mi)
1114 int screen = MI_SCREEN(mi);
1115 slideshow_state *ss;
1116 int wire = MI_IS_WIREFRAME(mi);
1119 if ((sss = (slideshow_state *)
1120 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1125 if ((ss->glx_context = init_GL(mi)) != NULL) {
1126 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1132 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1133 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1138 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1139 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1141 glDisable (GL_LIGHTING);
1142 glDisable (GL_DEPTH_TEST);
1143 glDepthMask (GL_FALSE);
1144 glEnable (GL_CULL_FACE);
1145 glCullFace (GL_BACK);
1149 glEnable (GL_TEXTURE_2D);
1150 glShadeModel (GL_SMOOTH);
1151 glEnable (GL_BLEND);
1152 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1155 if (debug_p) glLineWidth (3);
1157 load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1162 ss->now = double_time();
1163 ss->dawn_of_time = ss->now;
1164 ss->prev_frame_time = ss->now;
1166 await_first_image (mi); /* wait for first image to fully load */
1168 ss->now = double_time();
1169 ss->dawn_of_time = ss->now;
1171 new_sprite (mi); /* start first sprite fading in */
1176 draw_slideshow (ModeInfo *mi)
1178 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1181 if (!ss->glx_context)
1184 ss->now = double_time();
1186 /* Each sprite has three states: fading in, full, fading out.
1187 The in/out states overlap like this:
1189 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1190 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1191 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1193 So as soon as a sprite goes into the "out" state, we create
1194 a new sprite (in the "in" state.)
1197 if (ss->nsprites > 2) abort();
1199 /* If a sprite is just entering the fade-out state,
1200 then add a new sprite in the fade-in state.
1202 for (i = 0; i < ss->nsprites; i++)
1204 sprite *sp = ss->sprites[i];
1205 if (sp->state != sp->prev_state &&
1206 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1212 /* Now garbage collect the dead sprites.
1214 for (i = 0; i < ss->nsprites; i++)
1216 sprite *sp = ss->sprites[i];
1217 if (sp->state == DEAD)
1219 destroy_sprite (mi, sp);
1224 /* We can only ever end up with no sprites at all if the machine is
1225 being really slow and we hopped states directly from FULL to DEAD
1226 without passing OUT... */
1227 if (ss->nsprites == 0)
1230 if (!ss->redisplay_needed_p)
1233 if (debug_p && ss->now - ss->prev_frame_time > 1)
1234 fprintf (stderr, "%s: static screen for %.1f secs\n",
1235 blurb(), ss->now - ss->prev_frame_time);
1239 ss->fps = fps_1 (mi);
1240 if (mi->fps_p) fps_2 (mi);
1243 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1244 ss->prev_frame_time = ss->now;
1245 ss->redisplay_needed_p = False;