1 /* glslideshow, Copyright (c) 2003-2014 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 texture_font_data *font_data; /* for printing image file names */
165 int sprite_id, image_id; /* debugging id counters */
172 static slideshow_state *sss = NULL;
175 /* Command-line arguments
177 static int fade_seconds; /* Duration in seconds of fade transitions.
178 If 0, jump-cut instead of fading. */
179 static int pan_seconds; /* Duration of each pan through an image. */
180 static int image_seconds; /* How many seconds until loading a new image. */
181 static int zoom; /* How far in to zoom when panning, in percent of
182 image size: that is, 75 means "when zoomed all
183 the way in, 75% of the image will be visible."
185 static int fps_cutoff; /* If the frame-rate falls below this, turn off
187 static Bool letterbox_p; /* When a loaded image is not the same aspect
188 ratio as the window, whether to display black
191 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
192 static Bool do_titles; /* Display image titles. */
193 static Bool debug_p; /* Be loud and do weird things. */
196 static XrmOptionDescRec opts[] = {
197 {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
198 {"-pan", ".panDuration", XrmoptionSepArg, 0 },
199 {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
200 {"-zoom", ".zoom", XrmoptionSepArg, 0 },
201 {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
202 {"-titles", ".titles", XrmoptionNoArg, "True" },
203 {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
204 {"-no-letterbox", ".letterbox", XrmoptionNoArg, "False" },
205 {"-clip", ".letterbox", XrmoptionNoArg, "False" },
206 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
207 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
208 {"-debug", ".debug", XrmoptionNoArg, "True" },
211 static argtype vars[] = {
212 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
213 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
214 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
215 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
216 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
217 { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
218 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
219 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
220 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
223 ENTRYPOINT ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
230 return "GLSlideshow";
232 static char buf[255];
233 time_t now = time ((time_t *) 0);
234 char *ct = (char *) ctime (&now);
235 int n = strlen(progname);
237 strncpy(buf, progname, n);
240 strncpy(buf+n, ct+11, 8);
241 strcpy(buf+n+9, ": ");
247 /* Returns the current time in seconds as a double.
253 # ifdef GETTIMEOFDAY_TWO_ARGS
255 gettimeofday(&now, &tzp);
260 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
264 static void image_loaded_cb (const char *filename, XRectangle *geom,
265 int image_width, int image_height,
266 int texture_width, int texture_height,
270 /* Allocate an image structure and start a file loading in the background.
273 alloc_image (ModeInfo *mi)
275 slideshow_state *ss = &sss[MI_SCREEN(mi)];
276 int wire = MI_IS_WIREFRAME(mi);
277 image *img = (image *) calloc (1, sizeof (*img));
279 img->id = ++ss->image_id;
280 img->loaded_p = False;
284 glGenTextures (1, &img->texid);
285 if (img->texid <= 0) abort();
287 ss->image_load_time = ss->now;
290 image_loaded_cb (0, 0, 0, 0, 0, 0, img);
292 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
293 0, 0, mipmap_p, img->texid, image_loaded_cb, img);
295 ss->images[ss->nimages++] = img;
296 if (ss->nimages >= countof(ss->images)) abort();
302 /* Callback that tells us that the texture has been loaded.
305 image_loaded_cb (const char *filename, XRectangle *geom,
306 int image_width, int image_height,
307 int texture_width, int texture_height,
310 image *img = (image *) closure;
311 ModeInfo *mi = img->mi;
312 /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
314 int wire = MI_IS_WIREFRAME(mi);
318 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
319 img->h = MI_HEIGHT (mi);
320 img->geom.width = img->w;
321 img->geom.height = img->h;
325 if (image_width == 0 || image_height == 0)
328 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
329 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
330 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
332 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
333 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
335 img->w = image_width;
336 img->h = image_height;
337 img->tw = texture_width;
338 img->th = texture_height;
340 img->title = (filename ? strdup (filename) : 0);
342 /* If the image's width doesn't come back as the width of the screen,
343 then the image must have been scaled down (due to insufficient
344 texture memory.) Scale up the coordinates to stretch the image
347 if (img->w != MI_WIDTH(mi))
349 double scale = (double) MI_WIDTH(mi) / img->w;
354 img->geom.x *= scale;
355 img->geom.y *= scale;
356 img->geom.width *= scale;
357 img->geom.height *= scale;
360 /* xscreensaver-getimage returns paths relative to the image directory
361 now, so leave the sub-directory part in. Unless it's an absolute path.
363 if (img->title && img->title[0] == '/')
365 /* 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, '.');
373 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
374 blurb(), img->id, (img->title ? img->title : "(null)"));
377 img->loaded_p = True;
382 /* Free the image and texture, after nobody is referencing it.
385 destroy_image (ModeInfo *mi, image *img)
387 slideshow_state *ss = &sss[MI_SCREEN(mi)];
388 Bool freed_p = False;
392 if (!img->loaded_p) abort();
393 if (!img->used_p) abort();
394 if (img->texid <= 0) abort();
395 if (img->refcount != 0) abort();
397 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
398 if (ss->images[i] == img)
401 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
402 ss->images[j] = ss->images[j+1];
409 if (!freed_p) abort();
412 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
413 blurb(), img->id, (img->title ? img->title : "(null)"));
415 if (img->title) free (img->title);
416 glDeleteTextures (1, &img->texid);
421 /* Return an image to use for a sprite.
422 If it's time for a new one, get a new one.
423 Otherwise, use an old one.
424 Might return 0 if the machine is really slow.
427 get_image (ModeInfo *mi)
429 slideshow_state *ss = &sss[MI_SCREEN(mi)];
431 double now = ss->now;
432 Bool want_new_p = (ss->change_now_p ||
433 ss->image_load_time + image_seconds <= now);
436 image *loading_img = 0;
439 for (i = 0; i < ss->nimages; i++)
441 image *img2 = ss->images[i];
445 else if (!img2->used_p)
451 if (want_new_p && new_img)
452 img = new_img, new_img = 0, ss->change_now_p = False;
454 img = old_img, old_img = 0;
456 img = new_img, new_img = 0, ss->change_now_p = False;
458 /* Make sure that there is always one unused image in the pipe.
460 if (!new_img && !loading_img)
467 /* Pick random starting and ending positions for the given sprite.
470 randomize_sprite (ModeInfo *mi, sprite *sp)
472 int vp_w = MI_WIDTH(mi);
473 int vp_h = MI_HEIGHT(mi);
474 int img_w = sp->img->geom.width;
475 int img_h = sp->img->geom.height;
477 double ratio = (double) img_h / img_w;
488 min_w = img_w * (float) vp_h / img_h;
491 max_w = min_w * 100 / zoom;
493 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
494 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
495 sp->from.h = sp->from.w * ratio;
496 sp->to.h = sp->to.w * ratio;
498 if (zoom == 100) /* only one box, and it is centered */
500 sp->from.x = (sp->from.w > vp_w
501 ? -(sp->from.w - vp_w) / 2
502 : (vp_w - sp->from.w) / 2);
503 sp->from.y = (sp->from.h > vp_h
504 ? -(sp->from.h - vp_h) / 2
505 : (vp_h - sp->from.h) / 2);
508 else /* position both boxes randomly */
510 sp->from.x = (sp->from.w > vp_w
511 ? -frand (sp->from.w - vp_w)
512 : frand (vp_w - sp->from.w));
513 sp->from.y = (sp->from.h > vp_h
514 ? -frand (sp->from.h - vp_h)
515 : frand (vp_h - sp->from.h));
516 sp->to.x = (sp->to.w > vp_w
517 ? -frand (sp->to.w - vp_w)
518 : frand (vp_w - sp->to.w));
519 sp->to.y = (sp->to.h > vp_h
520 ? -frand (sp->to.h - vp_h)
521 : frand (vp_h - sp->to.h));
531 /* Make sure the aspect ratios are within 0.001 of each other.
534 int r1 = 0.5 + (sp->from.w * 1000 / sp->from.h);
535 int r2 = 0.5 + (sp->to.w * 1000 / sp->to.h);
536 if (r1 < r2-1 || r1 > r2+1)
539 "%s: botched aspect: %f x %f (%d) vs %f x %f (%d): %s\n",
541 sp->from.w, sp->from.h, r1,
542 sp->to.w, sp->to.h, r2,
543 (sp->img->title ? sp->img->title : "[null]"));
559 /* Allocate a new sprite and start its animation going.
562 new_sprite (ModeInfo *mi)
564 slideshow_state *ss = &sss[MI_SCREEN(mi)];
565 image *img = get_image (mi);
570 /* Oops, no images yet! The machine is probably hurting bad.
571 Let's give it some time before thrashing again. */
576 sp = (sprite *) calloc (1, sizeof (*sp));
577 sp->id = ++ss->sprite_id;
578 sp->start_time = ss->now;
579 sp->state_time = sp->start_time;
580 sp->state = sp->prev_state = NEW;
584 sp->img->used_p = True;
586 ss->sprites[ss->nsprites++] = sp;
587 if (ss->nsprites >= countof(ss->sprites)) abort();
589 randomize_sprite (mi, sp);
595 /* Free the given sprite, and decrement the reference count on its image.
598 destroy_sprite (ModeInfo *mi, sprite *sp)
600 slideshow_state *ss = &sss[MI_SCREEN(mi)];
601 Bool freed_p = False;
606 if (sp->state != DEAD) abort();
609 if (!img->loaded_p) abort();
610 if (!img->used_p) abort();
611 if (img->refcount <= 0) abort();
613 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
614 if (ss->sprites[i] == sp)
617 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
618 ss->sprites[j] = ss->sprites[j+1];
625 if (!freed_p) abort();
630 if (img->refcount < 0) abort();
631 if (img->refcount == 0)
632 destroy_image (mi, img);
636 /* Updates the sprite for the current frame of the animation based on
637 its creation time compared to the current wall clock.
640 tick_sprite (ModeInfo *mi, sprite *sp)
642 slideshow_state *ss = &sss[MI_SCREEN(mi)];
643 image *img = sp->img;
644 double now = ss->now;
647 rect prev_rect = sp->current;
648 GLfloat prev_opacity = sp->opacity;
650 if (! sp->img) abort();
651 if (! img->loaded_p) abort();
653 secs = now - sp->start_time;
654 ratio = secs / (pan_seconds + fade_seconds);
655 if (ratio > 1) ratio = 1;
657 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
658 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
659 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
660 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
662 sp->prev_state = sp->state;
664 if (secs < fade_seconds)
667 sp->opacity = secs / (GLfloat) fade_seconds;
669 else if (secs < pan_seconds)
674 else if (secs < pan_seconds + fade_seconds)
677 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
685 if (sp->state != sp->prev_state &&
686 (sp->prev_state == IN ||
687 sp->prev_state == FULL))
689 double secs = now - sp->state_time;
693 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
695 (sp->prev_state == IN ? "fade" : "pan "),
698 sp->frame_count / secs,
699 ss->theoretical_fps);
701 sp->state_time = now;
707 if (sp->state != DEAD &&
708 (prev_rect.x != sp->current.x ||
709 prev_rect.y != sp->current.y ||
710 prev_rect.w != sp->current.w ||
711 prev_rect.h != sp->current.h ||
712 prev_opacity != sp->opacity))
713 ss->redisplay_needed_p = True;
717 /* Draw the given sprite at the phase of its animation dictated by
718 its creation time compared to the current wall clock.
721 draw_sprite (ModeInfo *mi, sprite *sp)
723 slideshow_state *ss = &sss[MI_SCREEN(mi)];
724 int wire = MI_IS_WIREFRAME(mi);
725 image *img = sp->img;
727 if (! sp->img) abort();
728 if (! img->loaded_p) abort();
732 glTranslatef (sp->current.x, sp->current.y, 0);
733 glScalef (sp->current.w, sp->current.h, 1);
735 if (wire) /* Draw a grid inside the box */
738 GLfloat dx = dy * img->w / img->h;
742 glColor4f (sp->opacity, 0, 0, 1);
744 glColor4f (0, 0, sp->opacity, 1);
747 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
748 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
750 for (y = 0; y < 1+dy; y += dy)
752 GLfloat yy = (y > 1 ? 1 : y);
753 for (x = 0.5; x < 1+dx; x += dx)
755 GLfloat xx = (x > 1 ? 1 : x);
756 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
757 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
759 for (x = 0.5; x > -dx; x -= dx)
761 GLfloat xx = (x < 0 ? 0 : x);
762 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
763 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
768 else /* Draw the texture quad */
770 GLfloat texw = img->geom.width / (GLfloat) img->tw;
771 GLfloat texh = img->geom.height / (GLfloat) img->th;
772 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
773 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
774 GLfloat texx2 = texx1 + texw;
775 GLfloat texy2 = texy1 + texh;
777 glBindTexture (GL_TEXTURE_2D, img->texid);
778 glColor4f (1, 1, 1, sp->opacity);
779 glNormal3f (0, 0, 1);
781 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
782 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
783 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
784 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
787 if (debug_p) /* Draw a border around the image */
789 if (!wire) glDisable (GL_TEXTURE_2D);
792 glColor4f (sp->opacity, 0, 0, 1);
794 glColor4f (0, 0, sp->opacity, 1);
796 glBegin (GL_LINE_LOOP);
797 glVertex3f (0, 0, 0);
798 glVertex3f (0, 1, 0);
799 glVertex3f (1, 1, 0);
800 glVertex3f (1, 0, 0);
803 if (!wire) glEnable (GL_TEXTURE_2D);
809 img->title && *img->title)
812 int y = mi->xgwa.height - 10;
813 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
814 print_gl_string (mi->dpy, ss->font_data,
815 mi->xgwa.width, mi->xgwa.height, x, y,
818 glColor4f (1, 1, 1, sp->opacity);
819 print_gl_string (mi->dpy, ss->font_data,
820 mi->xgwa.width, mi->xgwa.height, x, y,
828 if (!wire) glDisable (GL_TEXTURE_2D);
831 glColor4f (1, 0, 0, 1);
833 glColor4f (0, 0, 1, 1);
835 /* Draw the "from" and "to" boxes
837 glBegin (GL_LINE_LOOP);
838 glVertex3f (sp->from.x, sp->from.y, 0);
839 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
840 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
841 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
844 glBegin (GL_LINE_LOOP);
845 glVertex3f (sp->to.x, sp->to.y, 0);
846 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
847 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
848 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
851 if (!wire) glEnable (GL_TEXTURE_2D);
857 tick_sprites (ModeInfo *mi)
859 slideshow_state *ss = &sss[MI_SCREEN(mi)];
861 for (i = 0; i < ss->nsprites; i++)
862 tick_sprite (mi, ss->sprites[i]);
867 draw_sprites (ModeInfo *mi)
869 slideshow_state *ss = &sss[MI_SCREEN(mi)];
872 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
877 GLfloat rot = current_device_rotation();
878 glTranslatef (0.5, 0.5, 0);
879 glRotatef(rot, 0, 0, 1);
880 if ((rot > 45 && rot < 135) ||
881 (rot < -45 && rot > -135))
883 GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
884 glScalef (s, 1/s, 1);
886 glTranslatef (-0.5, -0.5, 0);
889 for (i = 0; i < ss->nsprites; i++)
890 draw_sprite (mi, ss->sprites[i]);
893 if (debug_p) /* draw a white box (the "screen") */
895 int wire = MI_IS_WIREFRAME(mi);
897 if (!wire) glDisable (GL_TEXTURE_2D);
899 glColor4f (1, 1, 1, 1);
900 glBegin (GL_LINE_LOOP);
901 glVertex3f (0, 0, 0);
902 glVertex3f (0, 1, 0);
903 glVertex3f (1, 1, 0);
904 glVertex3f (1, 0, 0);
907 if (!wire) glEnable (GL_TEXTURE_2D);
913 reshape_slideshow (ModeInfo *mi, int width, int height)
915 slideshow_state *ss = &sss[MI_SCREEN(mi)];
917 glViewport (0, 0, width, height);
918 glMatrixMode (GL_PROJECTION);
920 glMatrixMode (GL_MODELVIEW);
927 s *= (zoom / 100.0) * 0.75;
928 if (s < 0.1) s = 0.1;
932 glTranslatef (-0.5, -0.5, 0);
934 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
936 ss->redisplay_needed_p = True;
941 slideshow_handle_event (ModeInfo *mi, XEvent *event)
943 slideshow_state *ss = &sss[MI_SCREEN(mi)];
945 if (event->xany.type == Expose ||
946 event->xany.type == GraphicsExpose ||
947 event->xany.type == VisibilityNotify)
949 ss->redisplay_needed_p = True;
951 fprintf (stderr, "%s: exposure\n", blurb());
954 else if (screenhack_event_helper (MI_DISPLAY(mi), MI_WINDOW(mi), event))
956 ss->change_now_p = True;
964 /* Do some sanity checking on various user-supplied values, and make
965 sure they are all internally consistent.
968 sanity_check (ModeInfo *mi)
970 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
971 else if (zoom > 100) zoom = 100;
973 if (zoom == 100) /* with no zooming, there is no panning */
976 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
977 pan_seconds = fade_seconds;
979 if (pan_seconds == 0) /* no zero-length cycles, please... */
982 if (image_seconds < pan_seconds) /* we only change images at fade-time */
983 image_seconds = pan_seconds;
985 /* If we're not panning/zooming within the image, then there's no point
986 in crossfading the image with itself -- only do crossfades when changing
988 if (zoom == 100 && pan_seconds < image_seconds)
989 pan_seconds = image_seconds;
991 /* No need to use mipmaps if we're not changing the image size much */
992 if (zoom >= 80) mipmap_p = False;
994 if (fps_cutoff < 0) fps_cutoff = 0;
995 else if (fps_cutoff > 30) fps_cutoff = 30;
1000 check_fps (ModeInfo *mi)
1002 #ifndef HAVE_COCOA /* always assume Cocoa is fast enough */
1004 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1006 double start_time, end_time, wall_elapsed, frame_duration, fps;
1009 start_time = ss->now;
1010 end_time = double_time();
1011 frame_duration = end_time - start_time; /* time spent drawing this frame */
1012 ss->time_elapsed += frame_duration; /* time spent drawing all frames */
1013 ss->frames_elapsed++;
1015 wall_elapsed = end_time - ss->dawn_of_time;
1016 fps = ss->frames_elapsed / ss->time_elapsed;
1017 ss->theoretical_fps = fps;
1019 if (ss->checked_fps_p) return;
1021 if (wall_elapsed <= 8) /* too early to be sure */
1024 ss->checked_fps_p = True;
1026 if (fps >= fps_cutoff)
1030 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1031 blurb(), fps, ss->frames_elapsed, wall_elapsed);
1036 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1043 for (i = 0; i < ss->nsprites; i++)
1045 sprite *sp = ss->sprites[i];
1046 randomize_sprite (mi, sp);
1050 ss->redisplay_needed_p = True;
1052 /* Need this in case zoom changed. */
1053 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1054 #endif /* HAVE_COCOA */
1058 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1061 hack_resources (void)
1064 char *res = "desktopGrabber";
1065 char *val = get_string_resource (res, "DesktopGrabber");
1069 sprintf (buf1, "%.100s.%.100s", progclass, res);
1070 sprintf (buf2, "%.200s -v", val);
1072 value.size = strlen(buf2);
1073 XrmPutResource (&db, buf1, "String", &value);
1079 init_slideshow (ModeInfo *mi)
1081 int screen = MI_SCREEN(mi);
1082 slideshow_state *ss;
1083 int wire = MI_IS_WIREFRAME(mi);
1086 if ((sss = (slideshow_state *)
1087 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1092 if ((ss->glx_context = init_GL(mi)) != NULL) {
1093 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1099 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1100 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1105 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1106 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1108 glDisable (GL_LIGHTING);
1109 glDisable (GL_DEPTH_TEST);
1110 glDepthMask (GL_FALSE);
1111 glEnable (GL_CULL_FACE);
1112 glCullFace (GL_BACK);
1116 glEnable (GL_TEXTURE_2D);
1117 glShadeModel (GL_SMOOTH);
1118 glEnable (GL_BLEND);
1119 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1122 if (debug_p) glLineWidth (3);
1124 ss->font_data = load_texture_font (mi->dpy, "titleFont");
1129 ss->now = double_time();
1130 ss->dawn_of_time = ss->now;
1131 ss->prev_frame_time = ss->now;
1133 ss->awaiting_first_image_p = True;
1139 draw_slideshow (ModeInfo *mi)
1141 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1144 if (!ss->glx_context)
1147 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
1149 if (ss->awaiting_first_image_p)
1151 image *img = ss->images[0];
1156 ss->awaiting_first_image_p = False;
1157 ss->dawn_of_time = double_time();
1159 /* start the very first sprite fading in */
1163 ss->now = double_time();
1165 /* Each sprite has three states: fading in, full, fading out.
1166 The in/out states overlap like this:
1168 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1169 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1170 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1172 So as soon as a sprite goes into the "out" state, we create
1173 a new sprite (in the "in" state.)
1176 if (ss->nsprites > 2) abort();
1178 /* If a sprite is just entering the fade-out state,
1179 then add a new sprite in the fade-in state.
1181 for (i = 0; i < ss->nsprites; i++)
1183 sprite *sp = ss->sprites[i];
1184 if (sp->state != sp->prev_state &&
1185 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1191 /* Now garbage collect the dead sprites.
1193 for (i = 0; i < ss->nsprites; i++)
1195 sprite *sp = ss->sprites[i];
1196 if (sp->state == DEAD)
1198 destroy_sprite (mi, sp);
1203 /* We can only ever end up with no sprites at all if the machine is
1204 being really slow and we hopped states directly from FULL to DEAD
1205 without passing OUT... */
1206 if (ss->nsprites == 0)
1209 if (!ss->redisplay_needed_p)
1212 if (debug_p && ss->now - ss->prev_frame_time > 1)
1213 fprintf (stderr, "%s: static screen for %.1f secs\n",
1214 blurb(), ss->now - ss->prev_frame_time);
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)