1 /* glslideshow, Copyright (c) 2003-2008 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 (img->title) /* strip filename to part after last /. */
363 char *s = strrchr (img->title, '/');
364 if (s) strcpy (img->title, s+1);
368 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
369 blurb(), img->id, (img->title ? img->title : "(null)"));
372 img->loaded_p = True;
377 /* Free the image and texture, after nobody is referencing it.
380 destroy_image (ModeInfo *mi, image *img)
382 slideshow_state *ss = &sss[MI_SCREEN(mi)];
383 Bool freed_p = False;
387 if (!img->loaded_p) abort();
388 if (!img->used_p) abort();
389 if (img->texid <= 0) abort();
390 if (img->refcount != 0) abort();
392 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
393 if (ss->images[i] == img)
396 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
397 ss->images[j] = ss->images[j+1];
404 if (!freed_p) abort();
407 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
408 blurb(), img->id, (img->title ? img->title : "(null)"));
410 if (img->title) free (img->title);
411 glDeleteTextures (1, &img->texid);
416 /* Return an image to use for a sprite.
417 If it's time for a new one, get a new one.
418 Otherwise, use an old one.
419 Might return 0 if the machine is really slow.
422 get_image (ModeInfo *mi)
424 slideshow_state *ss = &sss[MI_SCREEN(mi)];
426 double now = ss->now;
427 Bool want_new_p = (ss->change_now_p ||
428 ss->image_load_time + image_seconds <= now);
431 image *loading_img = 0;
434 for (i = 0; i < ss->nimages; i++)
436 image *img2 = ss->images[i];
440 else if (!img2->used_p)
446 if (want_new_p && new_img)
447 img = new_img, new_img = 0, ss->change_now_p = False;
449 img = old_img, old_img = 0;
451 img = new_img, new_img = 0, ss->change_now_p = False;
453 /* Make sure that there is always one unused image in the pipe.
455 if (!new_img && !loading_img)
462 /* Pick random starting and ending positions for the given sprite.
465 randomize_sprite (ModeInfo *mi, sprite *sp)
467 int vp_w = MI_WIDTH(mi);
468 int vp_h = MI_HEIGHT(mi);
469 int img_w = sp->img->geom.width;
470 int img_h = sp->img->geom.height;
471 int min_w, min_h, max_w, max_h;
472 double ratio = (double) img_h / img_w;
484 min_h = img_h * (float) vp_w / img_w;
488 min_w = img_w * (float) vp_h / img_h;
493 max_w = min_w * 100 / zoom;
494 max_h = min_h * 100 / zoom;
496 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
497 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
498 sp->from.h = sp->from.w * ratio;
499 sp->to.h = sp->to.w * ratio;
501 if (zoom == 100) /* only one box, and it is centered */
503 sp->from.x = (sp->from.w > vp_w
504 ? -(sp->from.w - vp_w) / 2
505 : (vp_w - sp->from.w) / 2);
506 sp->from.y = (sp->from.h > vp_h
507 ? -(sp->from.h - vp_h) / 2
508 : (vp_h - sp->from.h) / 2);
511 else /* position both boxes randomly */
513 sp->from.x = (sp->from.w > vp_w
514 ? -frand (sp->from.w - vp_w)
515 : frand (vp_w - sp->from.w));
516 sp->from.y = (sp->from.h > vp_h
517 ? -frand (sp->from.h - vp_h)
518 : frand (vp_h - sp->from.h));
519 sp->to.x = (sp->to.w > vp_w
520 ? -frand (sp->to.w - vp_w)
521 : frand (vp_w - sp->to.w));
522 sp->to.y = (sp->to.h > vp_h
523 ? -frand (sp->to.h - vp_h)
524 : frand (vp_h - sp->to.h));
534 /* Make sure the aspect ratios are within 0.001 of each other.
537 int r1 = 0.5 + (sp->from.w * 1000 / sp->from.h);
538 int r2 = 0.5 + (sp->to.w * 1000 / sp->to.h);
539 if (r1 < r2-1 || r1 > r2+1)
542 "%s: botched aspect: %f x %f (%d) vs %f x %f (%d): %s\n",
544 sp->from.w, sp->from.h, r1,
545 sp->to.w, sp->to.h, r2,
546 (sp->img->title ? sp->img->title : "[null]"));
562 /* Allocate a new sprite and start its animation going.
565 new_sprite (ModeInfo *mi)
567 slideshow_state *ss = &sss[MI_SCREEN(mi)];
568 image *img = get_image (mi);
573 /* Oops, no images yet! The machine is probably hurting bad.
574 Let's give it some time before thrashing again. */
579 sp = (sprite *) calloc (1, sizeof (*sp));
580 sp->id = ++ss->sprite_id;
581 sp->start_time = ss->now;
582 sp->state_time = sp->start_time;
583 sp->state = sp->prev_state = NEW;
587 sp->img->used_p = True;
589 ss->sprites[ss->nsprites++] = sp;
590 if (ss->nsprites >= countof(ss->sprites)) abort();
592 randomize_sprite (mi, sp);
598 /* Free the given sprite, and decrement the reference count on its image.
601 destroy_sprite (ModeInfo *mi, sprite *sp)
603 slideshow_state *ss = &sss[MI_SCREEN(mi)];
604 Bool freed_p = False;
609 if (sp->state != DEAD) abort();
612 if (!img->loaded_p) abort();
613 if (!img->used_p) abort();
614 if (img->refcount <= 0) abort();
616 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
617 if (ss->sprites[i] == sp)
620 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
621 ss->sprites[j] = ss->sprites[j+1];
628 if (!freed_p) abort();
633 if (img->refcount < 0) abort();
634 if (img->refcount == 0)
635 destroy_image (mi, img);
639 /* Updates the sprite for the current frame of the animation based on
640 its creation time compared to the current wall clock.
643 tick_sprite (ModeInfo *mi, sprite *sp)
645 slideshow_state *ss = &sss[MI_SCREEN(mi)];
646 image *img = sp->img;
647 double now = ss->now;
650 rect prev_rect = sp->current;
651 GLfloat prev_opacity = sp->opacity;
653 if (! sp->img) abort();
654 if (! img->loaded_p) abort();
656 secs = now - sp->start_time;
657 ratio = secs / (pan_seconds + fade_seconds);
658 if (ratio > 1) ratio = 1;
660 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
661 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
662 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
663 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
665 sp->prev_state = sp->state;
667 if (secs < fade_seconds)
670 sp->opacity = secs / (GLfloat) fade_seconds;
672 else if (secs < pan_seconds)
677 else if (secs < pan_seconds + fade_seconds)
680 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
688 if (sp->state != sp->prev_state &&
689 (sp->prev_state == IN ||
690 sp->prev_state == FULL))
692 double secs = now - sp->state_time;
696 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
698 (sp->prev_state == IN ? "fade" : "pan "),
701 sp->frame_count / secs,
702 ss->theoretical_fps);
704 sp->state_time = now;
710 if (sp->state != DEAD &&
711 (prev_rect.x != sp->current.x ||
712 prev_rect.y != sp->current.y ||
713 prev_rect.w != sp->current.w ||
714 prev_rect.h != sp->current.h ||
715 prev_opacity != sp->opacity))
716 ss->redisplay_needed_p = True;
720 /* Draw the given sprite at the phase of its animation dictated by
721 its creation time compared to the current wall clock.
724 draw_sprite (ModeInfo *mi, sprite *sp)
726 slideshow_state *ss = &sss[MI_SCREEN(mi)];
727 int wire = MI_IS_WIREFRAME(mi);
728 image *img = sp->img;
730 if (! sp->img) abort();
731 if (! img->loaded_p) abort();
735 glTranslatef (sp->current.x, sp->current.y, 0);
736 glScalef (sp->current.w, sp->current.h, 1);
738 if (wire) /* Draw a grid inside the box */
741 GLfloat dx = dy * img->w / img->h;
745 glColor4f (sp->opacity, 0, 0, 1);
747 glColor4f (0, 0, sp->opacity, 1);
750 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
751 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
753 for (y = 0; y < 1+dy; y += dy)
755 GLfloat yy = (y > 1 ? 1 : y);
756 for (x = 0.5; x < 1+dx; x += dx)
758 GLfloat xx = (x > 1 ? 1 : x);
759 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
760 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
762 for (x = 0.5; x > -dx; x -= dx)
764 GLfloat xx = (x < 0 ? 0 : x);
765 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
766 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
771 else /* Draw the texture quad */
773 GLfloat texw = img->geom.width / (GLfloat) img->tw;
774 GLfloat texh = img->geom.height / (GLfloat) img->th;
775 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
776 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
777 GLfloat texx2 = texx1 + texw;
778 GLfloat texy2 = texy1 + texh;
780 glBindTexture (GL_TEXTURE_2D, img->texid);
781 glColor4f (1, 1, 1, sp->opacity);
782 glNormal3f (0, 0, 1);
784 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
785 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
786 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
787 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
790 if (debug_p) /* Draw a border around the image */
792 if (!wire) glDisable (GL_TEXTURE_2D);
795 glColor4f (sp->opacity, 0, 0, 1);
797 glColor4f (0, 0, sp->opacity, 1);
799 glBegin (GL_LINE_LOOP);
800 glVertex3f (0, 0, 0);
801 glVertex3f (0, 1, 0);
802 glVertex3f (1, 1, 0);
803 glVertex3f (1, 0, 0);
806 if (!wire) glEnable (GL_TEXTURE_2D);
812 img->title && *img->title)
815 int y = mi->xgwa.height - 10;
816 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
817 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
818 mi->xgwa.width, mi->xgwa.height, x, y,
821 glColor4f (1, 1, 1, sp->opacity);
822 print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
823 mi->xgwa.width, mi->xgwa.height, x, y,
831 if (!wire) glDisable (GL_TEXTURE_2D);
834 glColor4f (1, 0, 0, 1);
836 glColor4f (0, 0, 1, 1);
838 /* Draw the "from" and "to" boxes
840 glBegin (GL_LINE_LOOP);
841 glVertex3f (sp->from.x, sp->from.y, 0);
842 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
843 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
844 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
847 glBegin (GL_LINE_LOOP);
848 glVertex3f (sp->to.x, sp->to.y, 0);
849 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
850 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
851 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
854 if (!wire) glEnable (GL_TEXTURE_2D);
860 tick_sprites (ModeInfo *mi)
862 slideshow_state *ss = &sss[MI_SCREEN(mi)];
864 for (i = 0; i < ss->nsprites; i++)
865 tick_sprite (mi, ss->sprites[i]);
870 draw_sprites (ModeInfo *mi)
872 slideshow_state *ss = &sss[MI_SCREEN(mi)];
875 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
878 for (i = 0; i < ss->nsprites; i++)
879 draw_sprite (mi, ss->sprites[i]);
882 if (debug_p) /* draw a white box (the "screen") */
884 int wire = MI_IS_WIREFRAME(mi);
886 if (!wire) glDisable (GL_TEXTURE_2D);
888 glColor4f (1, 1, 1, 1);
889 glBegin (GL_LINE_LOOP);
890 glVertex3f (0, 0, 0);
891 glVertex3f (0, 1, 0);
892 glVertex3f (1, 1, 0);
893 glVertex3f (1, 0, 0);
896 if (!wire) glEnable (GL_TEXTURE_2D);
902 reshape_slideshow (ModeInfo *mi, int width, int height)
904 slideshow_state *ss = &sss[MI_SCREEN(mi)];
906 glViewport (0, 0, width, height);
907 glMatrixMode (GL_PROJECTION);
909 glMatrixMode (GL_MODELVIEW);
916 s *= (zoom / 100.0) * 0.75;
917 if (s < 0.1) s = 0.1;
921 glTranslatef (-0.5, -0.5, 0);
923 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
925 ss->redisplay_needed_p = True;
930 slideshow_handle_event (ModeInfo *mi, XEvent *event)
932 slideshow_state *ss = &sss[MI_SCREEN(mi)];
934 if (event->xany.type == ButtonPress &&
935 event->xbutton.button == Button1)
937 ss->change_now_p = True;
940 else if (event->xany.type == KeyPress)
944 XLookupString (&event->xkey, &c, 1, &keysym, 0);
945 if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
947 ss->change_now_p = True;
951 else if (event->xany.type == Expose ||
952 event->xany.type == GraphicsExpose ||
953 event->xany.type == VisibilityNotify)
955 ss->redisplay_needed_p = True;
957 fprintf (stderr, "%s: exposure\n", blurb());
965 /* Do some sanity checking on various user-supplied values, and make
966 sure they are all internally consistent.
969 sanity_check (ModeInfo *mi)
971 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
972 else if (zoom > 100) zoom = 100;
974 if (zoom == 100) /* with no zooming, there is no panning */
977 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
978 pan_seconds = fade_seconds;
980 if (pan_seconds == 0) /* no zero-length cycles, please... */
983 if (image_seconds < pan_seconds) /* we only change images at fade-time */
984 image_seconds = pan_seconds;
986 /* If we're not panning/zooming within the image, then there's no point
987 in crossfading the image with itself -- only do crossfades when changing
989 if (zoom == 100 && pan_seconds < image_seconds)
990 pan_seconds = image_seconds;
992 /* No need to use mipmaps if we're not changing the image size much */
993 if (zoom >= 80) mipmap_p = False;
995 if (fps_cutoff < 0) fps_cutoff = 0;
996 else if (fps_cutoff > 30) fps_cutoff = 30;
1001 check_fps (ModeInfo *mi)
1003 #ifndef HAVE_COCOA /* always assume Cocoa is fast enough */
1005 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1007 double start_time, end_time, wall_elapsed, frame_duration, fps;
1010 start_time = ss->now;
1011 end_time = double_time();
1012 frame_duration = end_time - start_time; /* time spent drawing this frame */
1013 ss->time_elapsed += frame_duration; /* time spent drawing all frames */
1014 ss->frames_elapsed++;
1016 wall_elapsed = end_time - ss->dawn_of_time;
1017 fps = ss->frames_elapsed / ss->time_elapsed;
1018 ss->theoretical_fps = fps;
1020 if (ss->checked_fps_p) return;
1022 if (wall_elapsed <= 8) /* too early to be sure */
1025 ss->checked_fps_p = True;
1027 if (fps >= fps_cutoff)
1031 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1032 blurb(), fps, ss->frames_elapsed, wall_elapsed);
1037 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1044 for (i = 0; i < ss->nsprites; i++)
1046 sprite *sp = ss->sprites[i];
1047 randomize_sprite (mi, sp);
1051 ss->redisplay_needed_p = True;
1053 /* Need this in case zoom changed. */
1054 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1055 #endif /* HAVE_COCOA */
1059 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1062 hack_resources (void)
1065 char *res = "desktopGrabber";
1066 char *val = get_string_resource (res, "DesktopGrabber");
1070 sprintf (buf1, "%.100s.%.100s", progclass, res);
1071 sprintf (buf2, "%.200s -v", val);
1073 value.size = strlen(buf2);
1074 XrmPutResource (&db, buf1, "String", &value);
1080 init_slideshow (ModeInfo *mi)
1082 int screen = MI_SCREEN(mi);
1083 slideshow_state *ss;
1084 int wire = MI_IS_WIREFRAME(mi);
1087 if ((sss = (slideshow_state *)
1088 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1093 if ((ss->glx_context = init_GL(mi)) != NULL) {
1094 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1100 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1101 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1106 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1107 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1109 glDisable (GL_LIGHTING);
1110 glDisable (GL_DEPTH_TEST);
1111 glDepthMask (GL_FALSE);
1112 glEnable (GL_CULL_FACE);
1113 glCullFace (GL_BACK);
1117 glEnable (GL_TEXTURE_2D);
1118 glShadeModel (GL_SMOOTH);
1119 glEnable (GL_BLEND);
1120 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1123 if (debug_p) glLineWidth (3);
1125 load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1130 ss->now = double_time();
1131 ss->dawn_of_time = ss->now;
1132 ss->prev_frame_time = ss->now;
1134 ss->awaiting_first_image_p = True;
1140 draw_slideshow (ModeInfo *mi)
1142 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1145 if (!ss->glx_context)
1148 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
1150 if (ss->awaiting_first_image_p)
1152 image *img = ss->images[0];
1157 ss->awaiting_first_image_p = False;
1158 ss->dawn_of_time = double_time();
1160 /* start the very first sprite fading in */
1164 ss->now = double_time();
1166 /* Each sprite has three states: fading in, full, fading out.
1167 The in/out states overlap like this:
1169 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1170 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1171 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1173 So as soon as a sprite goes into the "out" state, we create
1174 a new sprite (in the "in" state.)
1177 if (ss->nsprites > 2) abort();
1179 /* If a sprite is just entering the fade-out state,
1180 then add a new sprite in the fade-in state.
1182 for (i = 0; i < ss->nsprites; i++)
1184 sprite *sp = ss->sprites[i];
1185 if (sp->state != sp->prev_state &&
1186 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1192 /* Now garbage collect the dead sprites.
1194 for (i = 0; i < ss->nsprites; i++)
1196 sprite *sp = ss->sprites[i];
1197 if (sp->state == DEAD)
1199 destroy_sprite (mi, sp);
1204 /* We can only ever end up with no sprites at all if the machine is
1205 being really slow and we hopped states directly from FULL to DEAD
1206 without passing OUT... */
1207 if (ss->nsprites == 0)
1210 if (!ss->redisplay_needed_p)
1213 if (debug_p && ss->now - ss->prev_frame_time > 1)
1214 fprintf (stderr, "%s: static screen for %.1f secs\n",
1215 blurb(), ss->now - ss->prev_frame_time);
1219 ss->fps = fps_compute (mi->fpst, 0);
1220 if (mi->fps_p) do_fps (mi);
1223 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1224 ss->prev_frame_time = ss->now;
1225 ss->redisplay_needed_p = False;
1229 XSCREENSAVER_MODULE_2 ("GLSlideshow", glslideshow, slideshow)