1 /* glslideshow, Copyright (c) 2003-2012 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 # ifdef HAVE_GLBITMAP
164 XFontStruct *xfont; /* for printing image file names */
167 texture_font_data *font_data;
170 int sprite_id, image_id; /* debugging id counters */
177 static slideshow_state *sss = NULL;
180 /* Command-line arguments
182 static int fade_seconds; /* Duration in seconds of fade transitions.
183 If 0, jump-cut instead of fading. */
184 static int pan_seconds; /* Duration of each pan through an image. */
185 static int image_seconds; /* How many seconds until loading a new image. */
186 static int zoom; /* How far in to zoom when panning, in percent of
187 image size: that is, 75 means "when zoomed all
188 the way in, 75% of the image will be visible."
190 static int fps_cutoff; /* If the frame-rate falls below this, turn off
192 static Bool letterbox_p; /* When a loaded image is not the same aspect
193 ratio as the window, whether to display black
196 static Bool mipmap_p; /* Use mipmaps instead of single textures. */
197 static Bool do_titles; /* Display image titles. */
198 static Bool debug_p; /* Be loud and do weird things. */
201 static XrmOptionDescRec opts[] = {
202 {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
203 {"-pan", ".panDuration", XrmoptionSepArg, 0 },
204 {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
205 {"-zoom", ".zoom", XrmoptionSepArg, 0 },
206 {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
207 {"-titles", ".titles", XrmoptionNoArg, "True" },
208 {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
209 {"-no-letterbox", ".letterbox", XrmoptionNoArg, "False" },
210 {"-clip", ".letterbox", XrmoptionNoArg, "False" },
211 {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
212 {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
213 {"-debug", ".debug", XrmoptionNoArg, "True" },
216 static argtype vars[] = {
217 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
218 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
219 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
220 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
221 { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
222 { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
223 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
224 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
225 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
228 ENTRYPOINT ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
235 return "GLSlideshow";
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, ": ");
252 /* Returns the current time in seconds as a double.
258 # ifdef GETTIMEOFDAY_TWO_ARGS
260 gettimeofday(&now, &tzp);
265 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
269 static void image_loaded_cb (const char *filename, XRectangle *geom,
270 int image_width, int image_height,
271 int texture_width, int texture_height,
275 /* Allocate an image structure and start a file loading in the background.
278 alloc_image (ModeInfo *mi)
280 slideshow_state *ss = &sss[MI_SCREEN(mi)];
281 int wire = MI_IS_WIREFRAME(mi);
282 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 load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
298 0, 0, mipmap_p, img->texid, image_loaded_cb, img);
300 ss->images[ss->nimages++] = img;
301 if (ss->nimages >= countof(ss->images)) abort();
307 /* Callback that tells us that the texture has been loaded.
310 image_loaded_cb (const char *filename, XRectangle *geom,
311 int image_width, int image_height,
312 int texture_width, int texture_height,
315 image *img = (image *) closure;
316 ModeInfo *mi = img->mi;
317 /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
319 int wire = MI_IS_WIREFRAME(mi);
323 img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
324 img->h = MI_HEIGHT (mi);
325 img->geom.width = img->w;
326 img->geom.height = img->h;
330 if (image_width == 0 || image_height == 0)
333 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
334 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
335 mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
337 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
338 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
340 img->w = image_width;
341 img->h = image_height;
342 img->tw = texture_width;
343 img->th = texture_height;
345 img->title = (filename ? strdup (filename) : 0);
347 /* If the image's width doesn't come back as the width of the screen,
348 then the image must have been scaled down (due to insufficient
349 texture memory.) Scale up the coordinates to stretch the image
352 if (img->w != MI_WIDTH(mi))
354 double scale = (double) MI_WIDTH(mi) / img->w;
359 img->geom.x *= scale;
360 img->geom.y *= scale;
361 img->geom.width *= scale;
362 img->geom.height *= scale;
365 /* xscreensaver-getimage returns paths relative to the image directory
366 now, so leave the sub-directory part in. Unless it's an absolute path.
368 if (img->title && img->title[0] == '/')
370 /* strip filename to part between last "/" and last ".". */
371 char *s = strrchr (img->title, '/');
372 if (s) strcpy (img->title, s+1);
373 s = strrchr (img->title, '.');
378 fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
379 blurb(), img->id, (img->title ? img->title : "(null)"));
382 img->loaded_p = True;
387 /* Free the image and texture, after nobody is referencing it.
390 destroy_image (ModeInfo *mi, image *img)
392 slideshow_state *ss = &sss[MI_SCREEN(mi)];
393 Bool freed_p = False;
397 if (!img->loaded_p) abort();
398 if (!img->used_p) abort();
399 if (img->texid <= 0) abort();
400 if (img->refcount != 0) abort();
402 for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
403 if (ss->images[i] == img)
406 for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
407 ss->images[j] = ss->images[j+1];
414 if (!freed_p) abort();
417 fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
418 blurb(), img->id, (img->title ? img->title : "(null)"));
420 if (img->title) free (img->title);
421 glDeleteTextures (1, &img->texid);
426 /* Return an image to use for a sprite.
427 If it's time for a new one, get a new one.
428 Otherwise, use an old one.
429 Might return 0 if the machine is really slow.
432 get_image (ModeInfo *mi)
434 slideshow_state *ss = &sss[MI_SCREEN(mi)];
436 double now = ss->now;
437 Bool want_new_p = (ss->change_now_p ||
438 ss->image_load_time + image_seconds <= now);
441 image *loading_img = 0;
444 for (i = 0; i < ss->nimages; i++)
446 image *img2 = ss->images[i];
450 else if (!img2->used_p)
456 if (want_new_p && new_img)
457 img = new_img, new_img = 0, ss->change_now_p = False;
459 img = old_img, old_img = 0;
461 img = new_img, new_img = 0, ss->change_now_p = False;
463 /* Make sure that there is always one unused image in the pipe.
465 if (!new_img && !loading_img)
472 /* Pick random starting and ending positions for the given sprite.
475 randomize_sprite (ModeInfo *mi, sprite *sp)
477 int vp_w = MI_WIDTH(mi);
478 int vp_h = MI_HEIGHT(mi);
479 int img_w = sp->img->geom.width;
480 int img_h = sp->img->geom.height;
482 double ratio = (double) img_h / img_w;
493 min_w = img_w * (float) vp_h / img_h;
496 max_w = min_w * 100 / zoom;
498 sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
499 sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
500 sp->from.h = sp->from.w * ratio;
501 sp->to.h = sp->to.w * ratio;
503 if (zoom == 100) /* only one box, and it is centered */
505 sp->from.x = (sp->from.w > vp_w
506 ? -(sp->from.w - vp_w) / 2
507 : (vp_w - sp->from.w) / 2);
508 sp->from.y = (sp->from.h > vp_h
509 ? -(sp->from.h - vp_h) / 2
510 : (vp_h - sp->from.h) / 2);
513 else /* position both boxes randomly */
515 sp->from.x = (sp->from.w > vp_w
516 ? -frand (sp->from.w - vp_w)
517 : frand (vp_w - sp->from.w));
518 sp->from.y = (sp->from.h > vp_h
519 ? -frand (sp->from.h - vp_h)
520 : frand (vp_h - sp->from.h));
521 sp->to.x = (sp->to.w > vp_w
522 ? -frand (sp->to.w - vp_w)
523 : frand (vp_w - sp->to.w));
524 sp->to.y = (sp->to.h > vp_h
525 ? -frand (sp->to.h - vp_h)
526 : frand (vp_h - sp->to.h));
536 /* Make sure the aspect ratios are within 0.001 of each other.
539 int r1 = 0.5 + (sp->from.w * 1000 / sp->from.h);
540 int r2 = 0.5 + (sp->to.w * 1000 / sp->to.h);
541 if (r1 < r2-1 || r1 > r2+1)
544 "%s: botched aspect: %f x %f (%d) vs %f x %f (%d): %s\n",
546 sp->from.w, sp->from.h, r1,
547 sp->to.w, sp->to.h, r2,
548 (sp->img->title ? sp->img->title : "[null]"));
564 /* Allocate a new sprite and start its animation going.
567 new_sprite (ModeInfo *mi)
569 slideshow_state *ss = &sss[MI_SCREEN(mi)];
570 image *img = get_image (mi);
575 /* Oops, no images yet! The machine is probably hurting bad.
576 Let's give it some time before thrashing again. */
581 sp = (sprite *) calloc (1, sizeof (*sp));
582 sp->id = ++ss->sprite_id;
583 sp->start_time = ss->now;
584 sp->state_time = sp->start_time;
585 sp->state = sp->prev_state = NEW;
589 sp->img->used_p = True;
591 ss->sprites[ss->nsprites++] = sp;
592 if (ss->nsprites >= countof(ss->sprites)) abort();
594 randomize_sprite (mi, sp);
600 /* Free the given sprite, and decrement the reference count on its image.
603 destroy_sprite (ModeInfo *mi, sprite *sp)
605 slideshow_state *ss = &sss[MI_SCREEN(mi)];
606 Bool freed_p = False;
611 if (sp->state != DEAD) abort();
614 if (!img->loaded_p) abort();
615 if (!img->used_p) abort();
616 if (img->refcount <= 0) abort();
618 for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
619 if (ss->sprites[i] == sp)
622 for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
623 ss->sprites[j] = ss->sprites[j+1];
630 if (!freed_p) abort();
635 if (img->refcount < 0) abort();
636 if (img->refcount == 0)
637 destroy_image (mi, img);
641 /* Updates the sprite for the current frame of the animation based on
642 its creation time compared to the current wall clock.
645 tick_sprite (ModeInfo *mi, sprite *sp)
647 slideshow_state *ss = &sss[MI_SCREEN(mi)];
648 image *img = sp->img;
649 double now = ss->now;
652 rect prev_rect = sp->current;
653 GLfloat prev_opacity = sp->opacity;
655 if (! sp->img) abort();
656 if (! img->loaded_p) abort();
658 secs = now - sp->start_time;
659 ratio = secs / (pan_seconds + fade_seconds);
660 if (ratio > 1) ratio = 1;
662 sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
663 sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
664 sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
665 sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
667 sp->prev_state = sp->state;
669 if (secs < fade_seconds)
672 sp->opacity = secs / (GLfloat) fade_seconds;
674 else if (secs < pan_seconds)
679 else if (secs < pan_seconds + fade_seconds)
682 sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
690 if (sp->state != sp->prev_state &&
691 (sp->prev_state == IN ||
692 sp->prev_state == FULL))
694 double secs = now - sp->state_time;
698 "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
700 (sp->prev_state == IN ? "fade" : "pan "),
703 sp->frame_count / secs,
704 ss->theoretical_fps);
706 sp->state_time = now;
712 if (sp->state != DEAD &&
713 (prev_rect.x != sp->current.x ||
714 prev_rect.y != sp->current.y ||
715 prev_rect.w != sp->current.w ||
716 prev_rect.h != sp->current.h ||
717 prev_opacity != sp->opacity))
718 ss->redisplay_needed_p = True;
722 /* Draw the given sprite at the phase of its animation dictated by
723 its creation time compared to the current wall clock.
726 draw_sprite (ModeInfo *mi, sprite *sp)
728 slideshow_state *ss = &sss[MI_SCREEN(mi)];
729 int wire = MI_IS_WIREFRAME(mi);
730 image *img = sp->img;
732 if (! sp->img) abort();
733 if (! img->loaded_p) abort();
737 glTranslatef (sp->current.x, sp->current.y, 0);
738 glScalef (sp->current.w, sp->current.h, 1);
740 if (wire) /* Draw a grid inside the box */
743 GLfloat dx = dy * img->w / img->h;
747 glColor4f (sp->opacity, 0, 0, 1);
749 glColor4f (0, 0, sp->opacity, 1);
752 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
753 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
755 for (y = 0; y < 1+dy; y += dy)
757 GLfloat yy = (y > 1 ? 1 : y);
758 for (x = 0.5; x < 1+dx; x += dx)
760 GLfloat xx = (x > 1 ? 1 : x);
761 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
762 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
764 for (x = 0.5; x > -dx; x -= dx)
766 GLfloat xx = (x < 0 ? 0 : x);
767 glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
768 glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
773 else /* Draw the texture quad */
775 GLfloat texw = img->geom.width / (GLfloat) img->tw;
776 GLfloat texh = img->geom.height / (GLfloat) img->th;
777 GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
778 GLfloat texy1 = img->geom.y / (GLfloat) img->th;
779 GLfloat texx2 = texx1 + texw;
780 GLfloat texy2 = texy1 + texh;
782 glBindTexture (GL_TEXTURE_2D, img->texid);
783 glColor4f (1, 1, 1, sp->opacity);
784 glNormal3f (0, 0, 1);
786 glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
787 glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
788 glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
789 glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
792 if (debug_p) /* Draw a border around the image */
794 if (!wire) glDisable (GL_TEXTURE_2D);
797 glColor4f (sp->opacity, 0, 0, 1);
799 glColor4f (0, 0, sp->opacity, 1);
801 glBegin (GL_LINE_LOOP);
802 glVertex3f (0, 0, 0);
803 glVertex3f (0, 1, 0);
804 glVertex3f (1, 1, 0);
805 glVertex3f (1, 0, 0);
808 if (!wire) glEnable (GL_TEXTURE_2D);
814 img->title && *img->title)
817 int y = mi->xgwa.height - 10;
818 glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
819 print_gl_string (mi->dpy,
820 # ifdef HAVE_GLBITMAP
821 ss->xfont, ss->font_dlist,
825 mi->xgwa.width, mi->xgwa.height, x, y,
828 glColor4f (1, 1, 1, sp->opacity);
829 print_gl_string (mi->dpy,
830 # ifdef HAVE_GLBITMAP
831 ss->xfont, ss->font_dlist,
835 mi->xgwa.width, mi->xgwa.height, x, y,
843 if (!wire) glDisable (GL_TEXTURE_2D);
846 glColor4f (1, 0, 0, 1);
848 glColor4f (0, 0, 1, 1);
850 /* Draw the "from" and "to" boxes
852 glBegin (GL_LINE_LOOP);
853 glVertex3f (sp->from.x, sp->from.y, 0);
854 glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
855 glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
856 glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
859 glBegin (GL_LINE_LOOP);
860 glVertex3f (sp->to.x, sp->to.y, 0);
861 glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
862 glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
863 glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
866 if (!wire) glEnable (GL_TEXTURE_2D);
872 tick_sprites (ModeInfo *mi)
874 slideshow_state *ss = &sss[MI_SCREEN(mi)];
876 for (i = 0; i < ss->nsprites; i++)
877 tick_sprite (mi, ss->sprites[i]);
882 draw_sprites (ModeInfo *mi)
884 slideshow_state *ss = &sss[MI_SCREEN(mi)];
887 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
892 GLfloat rot = current_device_rotation();
893 glTranslatef (0.5, 0.5, 0);
894 glRotatef(rot, 0, 0, 1);
895 if ((rot > 45 && rot < 135) ||
896 (rot < -45 && rot > -135))
898 GLfloat s = MI_WIDTH(mi) / (GLfloat) MI_HEIGHT(mi);
899 glScalef (s, 1/s, 1);
901 glTranslatef (-0.5, -0.5, 0);
904 for (i = 0; i < ss->nsprites; i++)
905 draw_sprite (mi, ss->sprites[i]);
908 if (debug_p) /* draw a white box (the "screen") */
910 int wire = MI_IS_WIREFRAME(mi);
912 if (!wire) glDisable (GL_TEXTURE_2D);
914 glColor4f (1, 1, 1, 1);
915 glBegin (GL_LINE_LOOP);
916 glVertex3f (0, 0, 0);
917 glVertex3f (0, 1, 0);
918 glVertex3f (1, 1, 0);
919 glVertex3f (1, 0, 0);
922 if (!wire) glEnable (GL_TEXTURE_2D);
928 reshape_slideshow (ModeInfo *mi, int width, int height)
930 slideshow_state *ss = &sss[MI_SCREEN(mi)];
932 glViewport (0, 0, width, height);
933 glMatrixMode (GL_PROJECTION);
935 glMatrixMode (GL_MODELVIEW);
942 s *= (zoom / 100.0) * 0.75;
943 if (s < 0.1) s = 0.1;
947 glTranslatef (-0.5, -0.5, 0);
949 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
951 ss->redisplay_needed_p = True;
956 slideshow_handle_event (ModeInfo *mi, XEvent *event)
958 slideshow_state *ss = &sss[MI_SCREEN(mi)];
960 if (event->xany.type == ButtonPress &&
961 event->xbutton.button == Button1)
963 ss->change_now_p = True;
966 else if (event->xany.type == KeyPress)
970 XLookupString (&event->xkey, &c, 1, &keysym, 0);
971 if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
973 ss->change_now_p = True;
977 else if (event->xany.type == Expose ||
978 event->xany.type == GraphicsExpose ||
979 event->xany.type == VisibilityNotify)
981 ss->redisplay_needed_p = True;
983 fprintf (stderr, "%s: exposure\n", blurb());
991 /* Do some sanity checking on various user-supplied values, and make
992 sure they are all internally consistent.
995 sanity_check (ModeInfo *mi)
997 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
998 else if (zoom > 100) zoom = 100;
1000 if (zoom == 100) /* with no zooming, there is no panning */
1003 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
1004 pan_seconds = fade_seconds;
1006 if (pan_seconds == 0) /* no zero-length cycles, please... */
1009 if (image_seconds < pan_seconds) /* we only change images at fade-time */
1010 image_seconds = pan_seconds;
1012 /* If we're not panning/zooming within the image, then there's no point
1013 in crossfading the image with itself -- only do crossfades when changing
1015 if (zoom == 100 && pan_seconds < image_seconds)
1016 pan_seconds = image_seconds;
1018 /* No need to use mipmaps if we're not changing the image size much */
1019 if (zoom >= 80) mipmap_p = False;
1021 if (fps_cutoff < 0) fps_cutoff = 0;
1022 else if (fps_cutoff > 30) fps_cutoff = 30;
1027 check_fps (ModeInfo *mi)
1029 #ifndef HAVE_COCOA /* always assume Cocoa is fast enough */
1031 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1033 double start_time, end_time, wall_elapsed, frame_duration, fps;
1036 start_time = ss->now;
1037 end_time = double_time();
1038 frame_duration = end_time - start_time; /* time spent drawing this frame */
1039 ss->time_elapsed += frame_duration; /* time spent drawing all frames */
1040 ss->frames_elapsed++;
1042 wall_elapsed = end_time - ss->dawn_of_time;
1043 fps = ss->frames_elapsed / ss->time_elapsed;
1044 ss->theoretical_fps = fps;
1046 if (ss->checked_fps_p) return;
1048 if (wall_elapsed <= 8) /* too early to be sure */
1051 ss->checked_fps_p = True;
1053 if (fps >= fps_cutoff)
1057 "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
1058 blurb(), fps, ss->frames_elapsed, wall_elapsed);
1063 "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
1070 for (i = 0; i < ss->nsprites; i++)
1072 sprite *sp = ss->sprites[i];
1073 randomize_sprite (mi, sp);
1077 ss->redisplay_needed_p = True;
1079 /* Need this in case zoom changed. */
1080 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
1081 #endif /* HAVE_COCOA */
1085 /* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
1088 hack_resources (void)
1091 char *res = "desktopGrabber";
1092 char *val = get_string_resource (res, "DesktopGrabber");
1096 sprintf (buf1, "%.100s.%.100s", progclass, res);
1097 sprintf (buf2, "%.200s -v", val);
1099 value.size = strlen(buf2);
1100 XrmPutResource (&db, buf1, "String", &value);
1106 init_slideshow (ModeInfo *mi)
1108 int screen = MI_SCREEN(mi);
1109 slideshow_state *ss;
1110 int wire = MI_IS_WIREFRAME(mi);
1113 if ((sss = (slideshow_state *)
1114 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
1119 if ((ss->glx_context = init_GL(mi)) != NULL) {
1120 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
1126 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
1127 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1132 fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
1133 blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
1135 glDisable (GL_LIGHTING);
1136 glDisable (GL_DEPTH_TEST);
1137 glDepthMask (GL_FALSE);
1138 glEnable (GL_CULL_FACE);
1139 glCullFace (GL_BACK);
1143 glEnable (GL_TEXTURE_2D);
1144 glShadeModel (GL_SMOOTH);
1145 glEnable (GL_BLEND);
1146 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1149 if (debug_p) glLineWidth (3);
1151 #ifdef HAVE_GLBITMAP
1152 load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
1154 ss->font_data = load_texture_font (mi->dpy, "Font");
1160 ss->now = double_time();
1161 ss->dawn_of_time = ss->now;
1162 ss->prev_frame_time = ss->now;
1164 ss->awaiting_first_image_p = True;
1170 draw_slideshow (ModeInfo *mi)
1172 slideshow_state *ss = &sss[MI_SCREEN(mi)];
1175 if (!ss->glx_context)
1178 glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
1180 if (ss->awaiting_first_image_p)
1182 image *img = ss->images[0];
1187 ss->awaiting_first_image_p = False;
1188 ss->dawn_of_time = double_time();
1190 /* start the very first sprite fading in */
1194 ss->now = double_time();
1196 /* Each sprite has three states: fading in, full, fading out.
1197 The in/out states overlap like this:
1199 iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
1200 . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
1201 . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
1203 So as soon as a sprite goes into the "out" state, we create
1204 a new sprite (in the "in" state.)
1207 if (ss->nsprites > 2) abort();
1209 /* If a sprite is just entering the fade-out state,
1210 then add a new sprite in the fade-in state.
1212 for (i = 0; i < ss->nsprites; i++)
1214 sprite *sp = ss->sprites[i];
1215 if (sp->state != sp->prev_state &&
1216 sp->state == (fade_seconds == 0 ? DEAD : OUT))
1222 /* Now garbage collect the dead sprites.
1224 for (i = 0; i < ss->nsprites; i++)
1226 sprite *sp = ss->sprites[i];
1227 if (sp->state == DEAD)
1229 destroy_sprite (mi, sp);
1234 /* We can only ever end up with no sprites at all if the machine is
1235 being really slow and we hopped states directly from FULL to DEAD
1236 without passing OUT... */
1237 if (ss->nsprites == 0)
1240 if (!ss->redisplay_needed_p)
1243 if (debug_p && ss->now - ss->prev_frame_time > 1)
1244 fprintf (stderr, "%s: static screen for %.1f secs\n",
1245 blurb(), ss->now - ss->prev_frame_time);
1249 if (mi->fps_p) do_fps (mi);
1252 glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
1253 ss->prev_frame_time = ss->now;
1254 ss->redisplay_needed_p = False;
1258 XSCREENSAVER_MODULE_2 ("GLSlideshow", glslideshow, slideshow)