-/* glslideshow, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
+/* glslideshow, Copyright (c) 2003-2006 Jamie Zawinski <jwz@jwz.org>
* Loads a sequence of images and smoothly pans around them; crossfades
* when loading new images.
*
- * First version Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
- * based on flipscreen3d, Copyright (C) 2001 Ben Buxton (bb@cactii.net).
- *
- * Almost entirely rewritten by jwz, 21-Jun-2003.
+ * Originally written by Mike Oliphant <oliphant@gtk.org> (c) 2002, 2003.
+ * Rewritten by jwz, 21-Jun-2003.
+ * Rewritten by jwz again, 6-Feb-2005.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*
+ *****************************************************************************
+ *
+ * TODO:
+ *
+ * - When a new image is loaded, there is a glitch: animation pauses during
+ * the period when we're loading the image-to-fade-in. On fast (2GHz)
+ * machines, this stutter is short but noticable (usually around 1/10th
+ * second.) On slower machines, it can be much more pronounced.
+ * This turns out to be hard to fix...
+ *
+ * Image loading happens in three stages:
+ *
+ * 1: Fork a process and run xscreensaver-getimage in the background.
+ * This writes image data to a server-side X pixmap.
+ *
+ * 2: When that completes, a callback informs us that the pixmap is ready.
+ * We must then download the pixmap data from the server with XGetImage
+ * (or XShmGetImage.)
+ *
+ * 3: Once we have the bits, we must convert them from server-native bitmap
+ * layout to 32 bit RGBA in client-endianness, to make them usable as
+ * OpenGL textures.
+ *
+ * 4: We must actually construct a texture.
+ *
+ * So, the speed of step 1 doesn't really matter, since that happens in
+ * the background. But steps 2, 3, and 4 happen in *this* process, and
+ * cause the visible glitch.
+ *
+ * Step 2 can't be moved to another process without opening a second
+ * connection to the X server, which is pretty heavy-weight. (That would
+ * be possible, though; the other process could open an X connection,
+ * retrieve the pixmap, and feed it back to us through a pipe or
+ * something.)
+ *
+ * Step 3 might be able to be optimized by coding tuned versions of
+ * grab-ximage.c:copy_ximage() for the most common depths and bit orders.
+ * (Or by moving it into the other process along with step 2.)
+ *
+ * Step 4 is the hard one, though. It might be possible to speed up this
+ * step if there is some way to allow two GL processes share texture
+ * data. Unless, of course, all the time being consumed by step 4 is
+ * because the graphics pipeline is flooded, in which case, that other
+ * process would starve the screen anyway.
+ *
+ * Is it possible to use a single GLX context in a multithreaded way?
+ * Or use a second GLX context, but allow the two contexts to share data?
+ * I can't find any documentation about this.
+ *
+ * How does Apple do this with their MacOSX slideshow screen saver?
+ * Perhaps it's easier for them because their OpenGL libraries have
+ * thread support at a lower level?
*/
-#include <X11/Intrinsic.h>
-
-
-# define PROGCLASS "GLSlideshow"
-# define HACK_INIT init_slideshow
-# define HACK_DRAW draw_slideshow
-# define HACK_RESHAPE reshape_slideshow
-# define HACK_HANDLE_EVENT glslideshow_handle_event
-# define EVENT_MASK (ExposureMask|VisibilityChangeMask)
-# define slideshow_opts xlockmore_opts
-
-# define DEF_FADE_DURATION "2"
-# define DEF_PAN_DURATION "6"
-# define DEF_IMAGE_DURATION "30"
-# define DEF_ZOOM "75"
-# define DEF_FPS_CUTOFF "5"
-# define DEF_DEBUG "False"
-
#define DEFAULTS "*delay: 20000 \n" \
- "*fadeDuration: " DEF_FADE_DURATION "\n" \
- "*panDuration: " DEF_PAN_DURATION "\n" \
- "*imageDuration: " DEF_IMAGE_DURATION "\n" \
- "*zoom: " DEF_ZOOM "\n" \
- "*FPScutoff: " DEF_FPS_CUTOFF "\n" \
- "*debug : " DEF_DEBUG "\n" \
"*wireframe: False \n" \
"*showFPS: False \n" \
"*fpsSolid: True \n" \
- "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
-
+ "*useSHM: True \n" \
+ "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
+ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n" \
+ "*grabDesktopImages: False \n" \
+ "*chooseRandomImages: True \n"
+
+# define refresh_slideshow 0
+# define release_slideshow 0
# include "xlockmore.h"
#undef countof
#ifdef USE_GL
-#include <GL/glu.h>
-#include <math.h>
-#include <sys/time.h>
-#include <stdio.h>
-#include <stdlib.h>
+
+# define DEF_FADE_DURATION "2"
+# define DEF_PAN_DURATION "6"
+# define DEF_IMAGE_DURATION "30"
+# define DEF_ZOOM "75"
+# define DEF_FPS_CUTOFF "5"
+# define DEF_TITLES "False"
+# define DEF_LETTERBOX "True"
+# define DEF_DEBUG "False"
+# define DEF_MIPMAP "True"
+
#include "grab-ximage.h"
+#include "glxfonts.h"
typedef struct {
- GLfloat x, y, w, h;
+ double x, y, w, h;
} rect;
typedef struct {
- GLuint texid; /* which texture to draw */
- enum { IN, OUT, DEAD } state; /* how to draw it */
- rect from, to; /* the journey this quad is taking */
-} gls_quad;
-
+ ModeInfo *mi;
+ int id; /* unique number for debugging */
+ char *title; /* the filename of this image */
+ int w, h; /* size in pixels of the image */
+ int tw, th; /* size in pixels of the texture */
+ XRectangle geom; /* where in the image the bits are */
+ Bool loaded_p; /* whether the image has finished loading */
+ Bool used_p; /* whether the image has yet appeared
+ on screen */
+ GLuint texid; /* which texture contains the image */
+ int refcount; /* how many sprites refer to this image */
+} image;
+
+
+typedef enum { NEW, IN, FULL, OUT, DEAD } sprite_state;
typedef struct {
- GLXContext *glx_context;
- time_t start_time; /* when we started displaying this image */
+ int id; /* unique number for debugging */
+ image *img; /* which image this animation displays */
+ GLfloat opacity; /* how to render it */
+ double start_time; /* when this animation began */
+ rect from, to, current; /* the journey this image is taking */
+ sprite_state state; /* the state we're in right now */
+ sprite_state prev_state; /* the state we were in previously */
+ double state_time; /* time of last state change */
+ int frame_count; /* frames since last state change */
+} sprite;
- int motion_frames; /* how many frames each pan takes */
- int fade_frames; /* how many frames fading in/out takes */
- gls_quad quads[2]; /* the (up to) 2 quads we animate */
- GLuint texids[2]; /* textures: "old" and "new" */
- GLuint current_texid; /* the "new" one */
+typedef struct {
+ GLXContext *glx_context;
+ int nimages; /* how many images are loaded or loading now */
+ image *images[10]; /* pointers to the images */
- int img_w, img_h; /* Size (pixels) of currently-loaded image */
+ int nsprites; /* how many sprites are animating right now */
+ sprite *sprites[10]; /* pointers to the live sprites */
double now; /* current time in seconds */
- double pan_start_time; /* when this pan began */
- double image_start_time; /* when this image was loaded */
double dawn_of_time; /* when the program launched */
+ double image_load_time; /* time when we last loaded a new image */
+ double prev_frame_time; /* time when we last drew a frame */
+ Bool awaiting_first_image_p; /* Early in startup: nothing to display yet */
Bool redisplay_needed_p; /* Sometimes we can get away with not
re-painting. Tick this if a redisplay
is required. */
+ Bool change_now_p; /* Set when the user clicks to ask for a new
+ image right now. */
GLfloat fps; /* approximate frame rate we're achieving */
- int pan_frame_count; /* More frame-rate stats */
- int fade_frame_count;
- Bool low_fps_p; /* Whether we have compensated for a low
+ GLfloat theoretical_fps; /* maximum frame rate that might be possible */
+ Bool checked_fps_p; /* Whether we have checked for a low
frame rate. */
+ XFontStruct *xfont; /* for printing image file names */
+ GLuint font_dlist;
+
+ int sprite_id, image_id; /* debugging id counters */
+
+ double time_elapsed;
+ int frames_elapsed;
+
} slideshow_state;
static slideshow_state *sss = NULL;
/* Command-line arguments
*/
-int fade_seconds; /* Duration in seconds of fade transitions.
- If 0, jump-cut instead of fading. */
-int pan_seconds; /* Duration of each pan through an image. */
-int image_seconds; /* How many seconds until loading a new image. */
-int zoom; /* How far in to zoom when panning, in percent of image
- size: that is, 75 means "when zoomed all the way in,
- 75% of the image will be on screen." */
-int fps_cutoff; /* If the frame-rate falls below this, turn off zooming.*/
-Bool debug_p; /* Be loud and do weird things. */
+static int fade_seconds; /* Duration in seconds of fade transitions.
+ If 0, jump-cut instead of fading. */
+static int pan_seconds; /* Duration of each pan through an image. */
+static int image_seconds; /* How many seconds until loading a new image. */
+static int zoom; /* How far in to zoom when panning, in percent of
+ image size: that is, 75 means "when zoomed all
+ the way in, 75% of the image will be visible."
+ */
+static int fps_cutoff; /* If the frame-rate falls below this, turn off
+ zooming.*/
+static Bool letterbox_p; /* When a loaded image is not the same aspect
+ ratio as the window, whether to display black
+ bars.
+ */
+static Bool mipmap_p; /* Use mipmaps instead of single textures. */
+static Bool do_titles; /* Display image titles. */
+static Bool debug_p; /* Be loud and do weird things. */
static XrmOptionDescRec opts[] = {
- {"-fade", ".slideshow.fadeDuration", XrmoptionSepArg, 0 },
- {"-pan", ".slideshow.panDuration", XrmoptionSepArg, 0 },
- {"-duration", ".slideshow.imageDuration", XrmoptionSepArg, 0 },
- {"-zoom", ".slideshow.zoom", XrmoptionSepArg, 0 },
- {"-cutoff", ".slideshow.FPScutoff", XrmoptionSepArg, 0 },
- {"-debug", ".slideshow.debug", XrmoptionNoArg, "True" },
+ {"-fade", ".fadeDuration", XrmoptionSepArg, 0 },
+ {"-pan", ".panDuration", XrmoptionSepArg, 0 },
+ {"-duration", ".imageDuration", XrmoptionSepArg, 0 },
+ {"-zoom", ".zoom", XrmoptionSepArg, 0 },
+ {"-cutoff", ".FPScutoff", XrmoptionSepArg, 0 },
+ {"-titles", ".titles", XrmoptionNoArg, "True" },
+ {"-letterbox", ".letterbox", XrmoptionNoArg, "True" },
+ {"-no-letterbox", ".letterbox", XrmoptionNoArg, "False" },
+ {"-clip", ".letterbox", XrmoptionNoArg, "False" },
+ {"-mipmaps", ".mipmap", XrmoptionNoArg, "True" },
+ {"-no-mipmaps", ".mipmap", XrmoptionNoArg, "False" },
+ {"-debug", ".debug", XrmoptionNoArg, "True" },
};
static argtype vars[] = {
{ &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
{ &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
{ &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
+ { &mipmap_p, "mipmap", "Mipmap", DEF_MIPMAP, t_Bool},
+ { &letterbox_p, "letterbox", "Letterbox", DEF_LETTERBOX, t_Bool},
{ &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
{ &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
+ { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
};
-ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
+ENTRYPOINT ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
+
+
+static const char *
+blurb (void)
+{
+# ifdef HAVE_COCOA
+ return "GLSlideshow";
+# else
+ static char buf[255];
+ time_t now = time ((time_t *) 0);
+ char *ct = (char *) ctime (&now);
+ int n = strlen(progname);
+ if (n > 100) n = 99;
+ strncpy(buf, progname, n);
+ buf[n++] = ':';
+ buf[n++] = ' ';
+ strncpy(buf+n, ct+11, 8);
+ strcpy(buf+n+9, ": ");
+ return buf;
+# endif
+}
/* Returns the current time in seconds as a double.
}
-static void
-draw_quad (ModeInfo *mi, gls_quad *q)
+static void image_loaded_cb (const char *filename, XRectangle *geom,
+ int image_width, int image_height,
+ int texture_width, int texture_height,
+ void *closure);
+
+
+/* Allocate an image structure and start a file loading in the background.
+ */
+static image *
+alloc_image (ModeInfo *mi)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
int wire = MI_IS_WIREFRAME(mi);
- GLfloat ratio;
- rect current;
- GLfloat opacity;
- double secs;
- GLfloat texw = 0;
- GLfloat texh = 0;
-
- if (q->state == DEAD)
- return;
+ image *img = (image *) calloc (1, sizeof (*img));
- secs = ss->now - ss->pan_start_time;
+ img->id = ++ss->image_id;
+ img->loaded_p = False;
+ img->used_p = False;
+ img->mi = mi;
- if (q->state == OUT)
- secs += pan_seconds;
+ glGenTextures (1, &img->texid);
+ if (img->texid <= 0) abort();
- ratio = secs / (pan_seconds + fade_seconds);
-
- current.x = q->from.x + ratio * (q->to.x - q->from.x);
- current.y = q->from.y + ratio * (q->to.y - q->from.y);
- current.w = q->from.w + ratio * (q->to.w - q->from.w);
- current.h = q->from.h + ratio * (q->to.h - q->from.h);
+ ss->image_load_time = ss->now;
- if (secs < fade_seconds)
- opacity = secs / (GLfloat) fade_seconds; /* fading in or out... */
- else if (secs < pan_seconds)
- opacity = 1; /* panning opaquely. */
+ if (wire)
+ image_loaded_cb (0, 0, 0, 0, 0, 0, img);
else
- opacity = 1 - ((secs - pan_seconds) /
- (GLfloat) fade_seconds); /* fading in or out... */
+ load_texture_async (mi->xgwa.screen, mi->window, *ss->glx_context,
+ 0, 0, mipmap_p, img->texid, image_loaded_cb, img);
- if (q->state == OUT && opacity < 0.0001)
- q->state = DEAD;
+ ss->images[ss->nimages++] = img;
+ if (ss->nimages >= countof(ss->images)) abort();
- glPushMatrix();
-
- glTranslatef (current.x, current.y, 0);
- glScalef (current.w, current.h, 1);
+ return img;
+}
- if (!wire)
- {
- texw = mi->xgwa.width / (GLfloat) ss->img_w;
- texh = mi->xgwa.height / (GLfloat) ss->img_h;
- glEnable (GL_TEXTURE_2D);
- glEnable (GL_BLEND);
- glBindTexture (GL_TEXTURE_2D, q->texid);
- glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glDepthMask (GL_FALSE);
-
- /* Draw the texture quad
- */
- glColor4f (1, 1, 1, opacity);
- glNormal3f (0, 0, 1);
- glBegin (GL_QUADS);
- glTexCoord2f (0, 0); glVertex3f (0, 0, 0);
- glTexCoord2f (0, texh); glVertex3f (0, 1, 0);
- glTexCoord2f (texw, texh); glVertex3f (1, 1, 0);
- glTexCoord2f (texw, 0); glVertex3f (1, 0, 0);
- glEnd();
+/* Callback that tells us that the texture has been loaded.
+ */
+static void
+image_loaded_cb (const char *filename, XRectangle *geom,
+ int image_width, int image_height,
+ int texture_width, int texture_height,
+ void *closure)
+{
+ image *img = (image *) closure;
+ ModeInfo *mi = img->mi;
+ /* slideshow_state *ss = &sss[MI_SCREEN(mi)]; */
- glDisable (GL_TEXTURE_2D);
- glDisable (GL_BLEND);
- }
+ int wire = MI_IS_WIREFRAME(mi);
if (wire)
- glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
- (q->texid == ss->texids[0] ? 0 : opacity),
- opacity);
- else
- glColor4f (1, 1, 1, opacity);
+ {
+ img->w = MI_WIDTH (mi) * (0.5 + frand (1.0));
+ img->h = MI_HEIGHT (mi);
+ img->geom.width = img->w;
+ img->geom.height = img->h;
+ goto DONE;
+ }
+ if (image_width == 0 || image_height == 0)
+ exit (1);
- /* Draw a grid inside the box
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ mipmap_p ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
+
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+
+ img->w = image_width;
+ img->h = image_height;
+ img->tw = texture_width;
+ img->th = texture_height;
+ img->geom = *geom;
+ img->title = (filename ? strdup (filename) : 0);
+
+ /* If the image's width doesn't come back as the width of the screen,
+ then the image must have been scaled down (due to insufficient
+ texture memory.) Scale up the coordinates to stretch the image
+ to fill the window.
*/
- if (wire)
+ if (img->w != MI_WIDTH(mi))
{
- GLfloat d = 0.1;
- GLfloat x, y;
- glBegin(GL_LINES);
- glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
- glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
-
- for (y = 0; y < 1+d; y += d)
- for (x = 0; x < 1+d; x += d)
- {
- glVertex3f (0, y, 0); glVertex3f (1, y, 0);
- glVertex3f (x, 0, 0); glVertex3f (x, 1, 0);
- }
- glEnd();
+ double scale = (double) MI_WIDTH(mi) / img->w;
+ img->w *= scale;
+ img->h *= scale;
+ img->tw *= scale;
+ img->th *= scale;
+ img->geom.x *= scale;
+ img->geom.y *= scale;
+ img->geom.width *= scale;
+ img->geom.height *= scale;
}
- glPopMatrix();
-
- if (debug_p)
+ if (img->title) /* strip filename to part after last /. */
{
- /* Draw the "from" and "to" boxes
- */
- glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
- (q->texid == ss->texids[0] ? 0 : opacity),
- opacity);
+ char *s = strrchr (img->title, '/');
+ if (s) strcpy (img->title, s+1);
+ }
- glBegin (GL_LINE_LOOP);
- glVertex3f (q->from.x, q->from.y, 0);
- glVertex3f (q->from.x + q->from.w, q->from.y, 0);
- glVertex3f (q->from.x + q->from.w, q->from.y + q->from.h, 0);
- glVertex3f (q->from.x, q->from.y + q->from.h, 0);
- glEnd();
+ if (debug_p)
+ fprintf (stderr, "%s: loaded img %2d: \"%s\"\n",
+ blurb(), img->id, (img->title ? img->title : "(null)"));
+ DONE:
- glBegin (GL_LINE_LOOP);
- glVertex3f (q->to.x, q->to.y, 0);
- glVertex3f (q->to.x + q->to.w, q->to.y, 0);
- glVertex3f (q->to.x + q->to.w, q->to.y + q->to.h, 0);
- glVertex3f (q->to.x, q->to.y + q->to.h, 0);
- glEnd();
- }
+ img->loaded_p = True;
}
+
+/* Free the image and texture, after nobody is referencing it.
+ */
static void
-draw_quads (ModeInfo *mi)
+destroy_image (ModeInfo *mi, image *img)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
- GLfloat s, o;
+ Bool freed_p = False;
int i;
- glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ if (!img) abort();
+ if (!img->loaded_p) abort();
+ if (!img->used_p) abort();
+ if (img->texid <= 0) abort();
+ if (img->refcount != 0) abort();
+
+ for (i = 0; i < ss->nimages; i++) /* unlink it from the list */
+ if (ss->images[i] == img)
+ {
+ int j;
+ for (j = i; j < ss->nimages-1; j++) /* pull remainder forward */
+ ss->images[j] = ss->images[j+1];
+ ss->images[j] = 0;
+ ss->nimages--;
+ freed_p = True;
+ break;
+ }
+
+ if (!freed_p) abort();
- glPushMatrix();
+ if (debug_p)
+ fprintf (stderr, "%s: unloaded img %2d: \"%s\"\n",
+ blurb(), img->id, (img->title ? img->title : "(null)"));
- s = (100.0 / zoom);
- o = (1-s)/2;
- glTranslatef (o, o, 0);
- glScalef (s, s, s);
+ if (img->title) free (img->title);
+ glDeleteTextures (1, &img->texid);
+ free (img);
+}
- for (i = 0; i < countof(ss->quads); i++)
- draw_quad (mi, &ss->quads[i]);
- glPopMatrix();
+/* Return an image to use for a sprite.
+ If it's time for a new one, get a new one.
+ Otherwise, use an old one.
+ Might return 0 if the machine is really slow.
+ */
+static image *
+get_image (ModeInfo *mi)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = 0;
+ double now = ss->now;
+ Bool want_new_p = (ss->change_now_p ||
+ ss->image_load_time + image_seconds <= now);
+ image *new_img = 0;
+ image *old_img = 0;
+ image *loading_img = 0;
+ int i;
- if (debug_p)
+ for (i = 0; i < ss->nimages; i++)
{
- glColor4f (1, 1, 1, 1);
- glBegin (GL_LINE_LOOP);
- glVertex3f (0, 0, 0);
- glVertex3f (0, 1, 0);
- glVertex3f (1, 1, 0);
- glVertex3f (1, 0, 0);
- glEnd();
+ image *img2 = ss->images[i];
+ if (!img2) abort();
+ if (!img2->loaded_p)
+ loading_img = img2;
+ else if (!img2->used_p)
+ new_img = img2;
+ else
+ old_img = img2;
}
+
+ if (want_new_p && new_img)
+ img = new_img, new_img = 0, ss->change_now_p = False;
+ else if (old_img)
+ img = old_img, old_img = 0;
+ else if (new_img)
+ img = new_img, new_img = 0, ss->change_now_p = False;
+
+ /* Make sure that there is always one unused image in the pipe.
+ */
+ if (!new_img && !loading_img)
+ alloc_image (mi);
+
+ return img;
}
-/* Re-randomize the state of the given quad.
+/* Pick random starting and ending positions for the given sprite.
*/
static void
-reset_quad (ModeInfo *mi, gls_quad *q)
+randomize_sprite (ModeInfo *mi, sprite *sp)
{
-/* slideshow_state *ss = &sss[MI_SCREEN(mi)];*/
-
- GLfloat mid_w = (zoom / 100.0);
- GLfloat mid_h = (zoom / 100.0);
- GLfloat mid_x = (1 - mid_w) / 2;
- GLfloat mid_y = (1 - mid_h) / 2;
+ int vp_w = MI_WIDTH(mi);
+ int vp_h = MI_HEIGHT(mi);
+ int img_w = sp->img->geom.width;
+ int img_h = sp->img->geom.height;
+ int min_w, min_h, max_w, max_h;
+ double ratio = (double) img_h / img_w;
+
+ if (letterbox_p)
+ {
+ min_w = img_w;
+ min_h = img_h;
+ }
+ else
+ {
+ if (img_w < vp_w)
+ {
+ min_w = vp_w;
+ min_h = img_h * (float) vp_w / img_w;
+ }
+ else
+ {
+ min_w = img_w * (float) vp_h / img_h;
+ min_h = vp_h;
+ }
+ }
- GLfloat small = mid_w + frand ((1 - mid_w) * 0.3);
-#if 0
- GLfloat large = small + frand ((1 - small) / 2) + ((1 - small) / 2);
-#else
- GLfloat large = small + frand (1 - small);
-#endif
+ max_w = min_w * 100 / zoom;
+ max_h = min_h * 100 / zoom;
- if (q->state != DEAD)
- abort(); /* we should only be resetting a quad when it's not visible. */
+ sp->from.w = min_w + frand ((max_w - min_w) * 0.4);
+ sp->to.w = max_w - frand ((max_w - min_w) * 0.4);
+ sp->from.h = sp->from.w * ratio;
+ sp->to.h = sp->to.w * ratio;
- /* Possible box sizes range between "zoom" and "100%".
- Pick a small box size, and a large box size.
- Assign each a random position within the 1x1 box,
- such that they encompass the middle "zoom" percentage.
- One of those is the start, and one is the end.
- Each frame will transition between one and the other.
- */
+ if (zoom == 100) /* only one box, and it is centered */
+ {
+ sp->from.x = (sp->from.w > vp_w
+ ? -(sp->from.w - vp_w) / 2
+ : (vp_w - sp->from.w) / 2);
+ sp->from.y = (sp->from.h > vp_h
+ ? -(sp->from.h - vp_h) / 2
+ : (vp_h - sp->from.h) / 2);
+ sp->to = sp->from;
+ }
+ else /* position both boxes randomly */
+ {
+ sp->from.x = (sp->from.w > vp_w
+ ? -frand (sp->from.w - vp_w)
+ : frand (vp_w - sp->from.w));
+ sp->from.y = (sp->from.h > vp_h
+ ? -frand (sp->from.h - vp_h)
+ : frand (vp_h - sp->from.h));
+ sp->to.x = (sp->to.w > vp_w
+ ? -frand (sp->to.w - vp_w)
+ : frand (vp_w - sp->to.w));
+ sp->to.y = (sp->to.h > vp_h
+ ? -frand (sp->to.h - vp_h)
+ : frand (vp_h - sp->to.h));
+ }
if (random() & 1)
{
- q->from.w = small; q->from.h = small;
- q->to.w = large; q->to.h = large;
+ rect swap = sp->to;
+ sp->to = sp->from;
+ sp->from = swap;
}
- else
+
+ /* Make sure the aspect ratios are within 0.0001 of each other.
+ */
+ if ((int) (0.5 + (sp->from.w * 1000 / sp->from.h)) !=
+ (int) (0.5 + (sp->to.w * 1000 / sp->to.h)))
+ abort();
+
+ sp->from.x /= vp_w;
+ sp->from.y /= vp_h;
+ sp->from.w /= vp_w;
+ sp->from.h /= vp_h;
+ sp->to.x /= vp_w;
+ sp->to.y /= vp_h;
+ sp->to.w /= vp_w;
+ sp->to.h /= vp_h;
+}
+
+
+/* Allocate a new sprite and start its animation going.
+ */
+static sprite *
+new_sprite (ModeInfo *mi)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = get_image (mi);
+ sprite *sp;
+
+ if (!img)
{
- q->from.w = large; q->from.h = large;
- q->to.w = small; q->to.h = small;
+ /* Oops, no images yet! The machine is probably hurting bad.
+ Let's give it some time before thrashing again. */
+ usleep (250000);
+ return 0;
}
- q->from.x = mid_x - frand (q->from.w - mid_w);
- q->from.y = mid_y - frand (q->from.h - mid_h);
- q->to.x = mid_x - frand (q->to.w - mid_w);
- q->to.y = mid_y - frand (q->to.w - mid_h);
+ sp = (sprite *) calloc (1, sizeof (*sp));
+ sp->id = ++ss->sprite_id;
+ sp->start_time = ss->now;
+ sp->state_time = sp->start_time;
+ sp->state = sp->prev_state = NEW;
+ sp->img = img;
+
+ sp->img->refcount++;
+ sp->img->used_p = True;
- q->state = IN;
+ ss->sprites[ss->nsprites++] = sp;
+ if (ss->nsprites >= countof(ss->sprites)) abort();
+
+ randomize_sprite (mi, sp);
+
+ return sp;
}
-/* Shrinks the XImage by a factor of two.
+/* Free the given sprite, and decrement the reference count on its image.
*/
static void
-shrink_image (ModeInfo *mi, XImage *ximage)
+destroy_sprite (ModeInfo *mi, sprite *sp)
{
- int w2 = ximage->width/2;
- int h2 = ximage->height/2;
- int x, y;
- XImage *ximage2;
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ Bool freed_p = False;
+ image *img;
+ int i;
- if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
- return;
+ if (!sp) abort();
+ if (sp->state != DEAD) abort();
+ img = sp->img;
+ if (!img) abort();
+ if (!img->loaded_p) abort();
+ if (!img->used_p) abort();
+ if (img->refcount <= 0) abort();
+
+ for (i = 0; i < ss->nsprites; i++) /* unlink it from the list */
+ if (ss->sprites[i] == sp)
+ {
+ int j;
+ for (j = i; j < ss->nsprites-1; j++) /* pull remainder forward */
+ ss->sprites[j] = ss->sprites[j+1];
+ ss->sprites[j] = 0;
+ ss->nsprites--;
+ freed_p = True;
+ break;
+ }
+
+ if (!freed_p) abort();
+ free (sp);
+ sp = 0;
+
+ img->refcount--;
+ if (img->refcount < 0) abort();
+ if (img->refcount == 0)
+ destroy_image (mi, img);
+}
- if (debug_p)
- fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
- progname, ximage->width, ximage->height, w2, h2);
-
- ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
- 32, ZPixmap, 0, 0,
- w2, h2, 32, 0);
- ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
- if (!ximage2->data)
+
+/* Updates the sprite for the current frame of the animation based on
+ its creation time compared to the current wall clock.
+ */
+static void
+tick_sprite (ModeInfo *mi, sprite *sp)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ image *img = sp->img;
+ double now = ss->now;
+ double secs;
+ double ratio;
+ rect prev_rect = sp->current;
+ GLfloat prev_opacity = sp->opacity;
+
+ if (! sp->img) abort();
+ if (! img->loaded_p) abort();
+
+ secs = now - sp->start_time;
+ ratio = secs / (pan_seconds + fade_seconds);
+ if (ratio > 1) ratio = 1;
+
+ sp->current.x = sp->from.x + ratio * (sp->to.x - sp->from.x);
+ sp->current.y = sp->from.y + ratio * (sp->to.y - sp->from.y);
+ sp->current.w = sp->from.w + ratio * (sp->to.w - sp->from.w);
+ sp->current.h = sp->from.h + ratio * (sp->to.h - sp->from.h);
+
+ sp->prev_state = sp->state;
+
+ if (secs < fade_seconds)
{
- fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
- progname, ximage->width, ximage->height, w2, h2);
- exit (1);
+ sp->state = IN;
+ sp->opacity = secs / (GLfloat) fade_seconds;
}
- for (y = 0; y < h2; y++)
- for (x = 0; x < w2; x++)
- XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
- free (ximage->data);
- *ximage = *ximage2;
- ximage2->data = 0;
- XFree (ximage2);
+ else if (secs < pan_seconds)
+ {
+ sp->state = FULL;
+ sp->opacity = 1;
+ }
+ else if (secs < pan_seconds + fade_seconds)
+ {
+ sp->state = OUT;
+ sp->opacity = 1 - ((secs - pan_seconds) / (GLfloat) fade_seconds);
+ }
+ else
+ {
+ sp->state = DEAD;
+ sp->opacity = 0;
+ }
+
+ if (sp->state != sp->prev_state &&
+ (sp->prev_state == IN ||
+ sp->prev_state == FULL))
+ {
+ double secs = now - sp->state_time;
+
+ if (debug_p)
+ fprintf (stderr,
+ "%s: %s %3d frames %2.0f sec %5.1f fps (%.1f fps?)\n",
+ blurb(),
+ (sp->prev_state == IN ? "fade" : "pan "),
+ sp->frame_count,
+ secs,
+ sp->frame_count / secs,
+ ss->theoretical_fps);
+
+ sp->state_time = now;
+ sp->frame_count = 0;
+ }
+
+ sp->frame_count++;
+
+ if (sp->state != DEAD &&
+ (prev_rect.x != sp->current.x ||
+ prev_rect.y != sp->current.y ||
+ prev_rect.w != sp->current.w ||
+ prev_rect.h != sp->current.h ||
+ prev_opacity != sp->opacity))
+ ss->redisplay_needed_p = True;
}
-/* Load a new image into a texture for the given quad.
+/* Draw the given sprite at the phase of its animation dictated by
+ its creation time compared to the current wall clock.
*/
static void
-load_quad (ModeInfo *mi, gls_quad *q)
+draw_sprite (ModeInfo *mi, sprite *sp)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
- XImage *ximage;
- int status;
- int max_reduction = 7;
- int err_count = 0;
int wire = MI_IS_WIREFRAME(mi);
+ image *img = sp->img;
- if (q->state != DEAD) abort();
+ if (! sp->img) abort();
+ if (! img->loaded_p) abort();
- /* Figure out which texid is currently in use, and pick the other one.
- */
+ glPushMatrix();
{
- GLuint tid = 0;
- int i;
- if (ss->current_texid == 0)
- tid = ss->texids[0];
- else
- for (i = 0; i < countof(ss->texids); i++)
- if (ss->texids[i] != ss->current_texid)
+ glTranslatef (sp->current.x, sp->current.y, 0);
+ glScalef (sp->current.w, sp->current.h, 1);
+
+ if (wire) /* Draw a grid inside the box */
+ {
+ GLfloat dy = 0.1;
+ GLfloat dx = dy * img->w / img->h;
+ GLfloat x, y;
+
+ if (sp->id & 1)
+ glColor4f (sp->opacity, 0, 0, 1);
+ else
+ glColor4f (0, 0, sp->opacity, 1);
+
+ glBegin(GL_LINES);
+ glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
+
+ for (y = 0; y < 1+dy; y += dy)
{
- tid = ss->texids[i];
- break;
+ GLfloat yy = (y > 1 ? 1 : y);
+ for (x = 0.5; x < 1+dx; x += dx)
+ {
+ GLfloat xx = (x > 1 ? 1 : x);
+ glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
+ glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
+ }
+ for (x = 0.5; x > -dx; x -= dx)
+ {
+ GLfloat xx = (x < 0 ? 0 : x);
+ glVertex3f (0, xx, 0); glVertex3f (1, xx, 0);
+ glVertex3f (yy, 0, 0); glVertex3f (yy, 1, 0);
+ }
}
+ glEnd();
+ }
+ else /* Draw the texture quad */
+ {
+ GLfloat texw = img->geom.width / (GLfloat) img->tw;
+ GLfloat texh = img->geom.height / (GLfloat) img->th;
+ GLfloat texx1 = img->geom.x / (GLfloat) img->tw;
+ GLfloat texy1 = img->geom.y / (GLfloat) img->th;
+ GLfloat texx2 = texx1 + texw;
+ GLfloat texy2 = texy1 + texh;
+
+ glBindTexture (GL_TEXTURE_2D, img->texid);
+ glColor4f (1, 1, 1, sp->opacity);
+ glNormal3f (0, 0, 1);
+ glBegin (GL_QUADS);
+ glTexCoord2f (texx1, texy2); glVertex3f (0, 0, 0);
+ glTexCoord2f (texx2, texy2); glVertex3f (1, 0, 0);
+ glTexCoord2f (texx2, texy1); glVertex3f (1, 1, 0);
+ glTexCoord2f (texx1, texy1); glVertex3f (0, 1, 0);
+ glEnd();
+
+ if (debug_p) /* Draw a border around the image */
+ {
+ if (!wire) glDisable (GL_TEXTURE_2D);
- if (tid == 0) abort(); /* both textures in use by visible quads? */
- q->texid = tid;
- ss->current_texid = tid;
+ if (sp->id & 1)
+ glColor4f (sp->opacity, 0, 0, 1);
+ else
+ glColor4f (0, 0, sp->opacity, 1);
+
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (0, 0, 0);
+ glVertex3f (0, 1, 0);
+ glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0);
+ glEnd();
+
+ if (!wire) glEnable (GL_TEXTURE_2D);
+ }
+ }
+
+
+ if (do_titles &&
+ img->title && *img->title)
+ {
+ int x = 10;
+ int y = mi->xgwa.height - 10;
+ glColor4f (0, 0, 0, sp->opacity); /* cheap-assed dropshadow */
+ print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
+ mi->xgwa.width, mi->xgwa.height, x, y,
+ img->title);
+ x++; y++;
+ glColor4f (1, 1, 1, sp->opacity);
+ print_gl_string (mi->dpy, ss->xfont, ss->font_dlist,
+ mi->xgwa.width, mi->xgwa.height, x, y,
+ img->title);
+ }
}
+ glPopMatrix();
if (debug_p)
- fprintf (stderr, "%s: debug: loading image %d (%dx%d)\n",
- progname, q->texid, mi->xgwa.width, mi->xgwa.height);
+ {
+ if (!wire) glDisable (GL_TEXTURE_2D);
- if (wire)
- goto DONE;
+ if (sp->id & 1)
+ glColor4f (1, 0, 0, 1);
+ else
+ glColor4f (0, 0, 1, 1);
- ximage = screen_to_ximage (mi->xgwa.screen, mi->window);
+ /* Draw the "from" and "to" boxes
+ */
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (sp->from.x, sp->from.y, 0);
+ glVertex3f (sp->from.x + sp->from.w, sp->from.y, 0);
+ glVertex3f (sp->from.x + sp->from.w, sp->from.y + sp->from.h, 0);
+ glVertex3f (sp->from.x, sp->from.y + sp->from.h, 0);
+ glEnd();
- glBindTexture (GL_TEXTURE_2D, q->texid);
- glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
- glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
- GL_LINEAR_MIPMAP_LINEAR);
-
- ss->img_w = ximage->width;
- ss->img_h = ximage->height;
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (sp->to.x, sp->to.y, 0);
+ glVertex3f (sp->to.x + sp->to.w, sp->to.y, 0);
+ glVertex3f (sp->to.x + sp->to.w, sp->to.y + sp->to.h, 0);
+ glVertex3f (sp->to.x, sp->to.y + sp->to.h, 0);
+ glEnd();
- AGAIN:
+ if (!wire) glEnable (GL_TEXTURE_2D);
+ }
+}
- clear_gl_error();
- status = gluBuild2DMipmaps (GL_TEXTURE_2D, 3,
- ximage->width, ximage->height,
- GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
-
- if(!status && glGetError())
- /* Some implementations of gluBuild2DMipmaps(), but set a GL error anyway.
- We could just call check_gl_error(), but that would exit. */
- status = -1;
- if (status)
- {
- char buf[100];
- const char *s = (char *) gluErrorString (status);
+static void
+tick_sprites (ModeInfo *mi)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
+ for (i = 0; i < ss->nsprites; i++)
+ tick_sprite (mi, ss->sprites[i]);
+}
- if (!s || !*s)
- {
- sprintf (buf, "unknown error %d", status);
- s = buf;
- }
- clear_gl_error();
+static void
+draw_sprites (ModeInfo *mi)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ int i;
- if (++err_count > max_reduction)
- {
- fprintf(stderr,
- "\n"
- "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
- "%s: GLU said: \"%s\".\n"
- "%s: probably this means "
- "\"your video card is worthless and weak\"?\n\n",
- progname, MI_WIDTH(mi), MI_HEIGHT(mi),
- ximage->width, ximage->height,
- progname, s,
- progname);
- exit (1);
- }
- else
- {
- if (debug_p)
- fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
- progname, ximage->width, ximage->height, s);
- shrink_image (mi, ximage);
- goto AGAIN;
- }
- }
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- check_gl_error("mipmapping"); /* should get a return code instead of a
- GL error, but just in case... */
+ glPushMatrix();
+ for (i = 0; i < ss->nsprites; i++)
+ draw_sprite (mi, ss->sprites[i]);
+ glPopMatrix();
- free(ximage->data);
- ximage->data = 0;
- XDestroyImage(ximage);
+ if (debug_p) /* draw a white box (the "screen") */
+ {
+ int wire = MI_IS_WIREFRAME(mi);
- DONE:
+ if (!wire) glDisable (GL_TEXTURE_2D);
- /* Re-set "now" so that time spent loading the image file does not count
- against the time remaining in this stage of the animation: image loading,
- if it takes a perceptible amount of time, will cause the animation to
- pause, but will not cause it to drop frames.
- */
- ss->now = double_time ();
- ss->image_start_time = ss->now;
+ glColor4f (1, 1, 1, 1);
+ glBegin (GL_LINE_LOOP);
+ glVertex3f (0, 0, 0);
+ glVertex3f (0, 1, 0);
+ glVertex3f (1, 1, 0);
+ glVertex3f (1, 0, 0);
+ glEnd();
- ss->redisplay_needed_p = True;
+ if (!wire) glEnable (GL_TEXTURE_2D);
+ }
}
-
-void
+ENTRYPOINT void
reshape_slideshow (ModeInfo *mi, int width, int height)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
}
-Bool
-glslideshow_handle_event (ModeInfo *mi, XEvent *event)
+ENTRYPOINT Bool
+slideshow_handle_event (ModeInfo *mi, XEvent *event)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
- if (event->xany.type == Expose ||
- event->xany.type == GraphicsExpose ||
- event->xany.type == VisibilityNotify)
+ if (event->xany.type == ButtonPress &&
+ event->xbutton.button == Button1)
{
- if (debug_p)
- fprintf (stderr, "%s: debug: exposure\n", progname);
- ss->redisplay_needed_p = True;
+ ss->change_now_p = True;
return True;
}
+ else if (event->xany.type == KeyPress)
+ {
+ KeySym keysym;
+ char c = 0;
+ XLookupString (&event->xkey, &c, 1, &keysym, 0);
+ if (c == ' ' || c == '\r' || c == '\n' || c == '\t')
+ {
+ ss->change_now_p = True;
+ return True;
+ }
+ }
+ else if (event->xany.type == Expose ||
+ event->xany.type == GraphicsExpose ||
+ event->xany.type == VisibilityNotify)
+ {
+ ss->redisplay_needed_p = True;
+ if (debug_p)
+ fprintf (stderr, "%s: exposure\n", blurb());
+ return False;
+ }
+
return False;
}
if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
else if (zoom > 100) zoom = 100;
+ if (zoom == 100) /* with no zooming, there is no panning */
+ pan_seconds = 0;
+
if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
pan_seconds = fade_seconds;
if (zoom == 100 && pan_seconds < image_seconds)
pan_seconds = image_seconds;
+ /* No need to use mipmaps if we're not changing the image size much */
+ if (zoom >= 80) mipmap_p = False;
+
if (fps_cutoff < 0) fps_cutoff = 0;
else if (fps_cutoff > 30) fps_cutoff = 30;
}
-void
+static void
+check_fps (ModeInfo *mi)
+{
+#ifndef HAVE_COCOA /* always assume Cocoa is fast enough */
+
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+
+ double start_time, end_time, wall_elapsed, frame_duration, fps;
+ int i;
+
+ start_time = ss->now;
+ end_time = double_time();
+ frame_duration = end_time - start_time; /* time spent drawing this frame */
+ ss->time_elapsed += frame_duration; /* time spent drawing all frames */
+ ss->frames_elapsed++;
+
+ wall_elapsed = end_time - ss->dawn_of_time;
+ fps = ss->frames_elapsed / ss->time_elapsed;
+ ss->theoretical_fps = fps;
+
+ if (ss->checked_fps_p) return;
+
+ if (wall_elapsed <= 8) /* too early to be sure */
+ return;
+
+ ss->checked_fps_p = True;
+
+ if (fps >= fps_cutoff)
+ {
+ if (debug_p)
+ fprintf (stderr,
+ "%s: %.1f fps is fast enough (with %d frames in %.1f secs)\n",
+ blurb(), fps, ss->frames_elapsed, wall_elapsed);
+ return;
+ }
+
+ fprintf (stderr,
+ "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
+ blurb(), fps);
+ zoom = 100;
+ fade_seconds = 0;
+
+ sanity_check (mi);
+
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp = ss->sprites[i];
+ randomize_sprite (mi, sp);
+ sp->state = FULL;
+ }
+
+ ss->redisplay_needed_p = True;
+
+ /* Need this in case zoom changed. */
+ reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
+#endif /* HAVE_COCOA */
+}
+
+
+/* Kludge to add "-v" to invocation of "xscreensaver-getimage" in -debug mode
+ */
+static void
+hack_resources (void)
+{
+#if 0
+ char *res = "desktopGrabber";
+ char *val = get_string_resource (res, "DesktopGrabber");
+ char buf1[255];
+ char buf2[255];
+ XrmValue value;
+ sprintf (buf1, "%.100s.%.100s", progclass, res);
+ sprintf (buf2, "%.200s -v", val);
+ value.addr = buf2;
+ value.size = strlen(buf2);
+ XrmPutResource (&db, buf1, "String", &value);
+#endif
+}
+
+
+ENTRYPOINT void
init_slideshow (ModeInfo *mi)
{
int screen = MI_SCREEN(mi);
slideshow_state *ss;
int wire = MI_IS_WIREFRAME(mi);
- int i;
if (sss == NULL) {
if ((sss = (slideshow_state *)
MI_CLEARWINDOW(mi);
}
+ if (debug_p)
+ fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
+ blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
+
sanity_check(mi);
if (debug_p)
- fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
- progname, pan_seconds, fade_seconds, image_seconds, zoom);
+ fprintf (stderr, "%s: pan: %d; fade: %d; img: %d; zoom: %d%%\n\n",
+ blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
+
+ glDisable (GL_LIGHTING);
+ glDisable (GL_DEPTH_TEST);
+ glDepthMask (GL_FALSE);
+ glEnable (GL_CULL_FACE);
+ glCullFace (GL_BACK);
if (! wire)
{
+ glEnable (GL_TEXTURE_2D);
glShadeModel (GL_SMOOTH);
- glPolygonMode (GL_FRONT_AND_BACK,GL_FILL);
- glEnable (GL_DEPTH_TEST);
- glEnable (GL_CULL_FACE);
- glCullFace (GL_FRONT);
- glDisable (GL_LIGHTING);
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
- ss->now = double_time ();
- ss->dawn_of_time = ss->now;
-
if (debug_p) glLineWidth (3);
- ss->pan_start_time = ss->now;
- ss->image_start_time = ss->now;
-
- for (i = 0; i < countof(ss->texids); i++)
- glGenTextures (1, &ss->texids[i]);
- ss->current_texid = 0;
-
- for (i = 0; i < countof(ss->quads); i++)
- {
- gls_quad *q = &ss->quads[i];
- q->texid = ss->current_texid;
- q->state = DEAD;
- reset_quad (mi, q);
- q->state = DEAD;
- }
-
- load_quad (mi, &ss->quads[0]);
- ss->quads[0].state = IN;
-
- ss->redisplay_needed_p = True;
-}
-
-
-/* Call this each time we change from one state to another.
- It gathers statistics on the frame rate of the previous state,
- and if it's bad, turn things off (under the assumption that
- we're running on sucky hardware.)
- */
-static void
-ponder_state_change (ModeInfo *mi)
-{
- slideshow_state *ss = &sss[MI_SCREEN(mi)];
- const char *which;
- int frames, secs;
- GLfloat fps;
-
- if (ss->fade_frame_count && ss->pan_frame_count)
- abort(); /* one of these should be zero! */
- else if (ss->fade_frame_count) /* just finished fading */
- {
- which = "faded ";
- secs = fade_seconds;
- frames = ss->fade_frame_count;
- ss->fade_frame_count = 0;
- }
- else if (ss->pan_frame_count) /* just finished panning */
- {
- which = "panned";
- secs = pan_seconds;
- frames = ss->pan_frame_count;
- ss->pan_frame_count = 0;
- }
- else
- abort(); /* one of these should be non-zero! */
-
- fps = frames / (GLfloat) secs;
+ load_font (mi->dpy, "titleFont", &ss->xfont, &ss->font_dlist);
if (debug_p)
- fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
- progname, which, frames, secs, fps);
-
-
- if (fps < fps_cutoff && !ss->low_fps_p) /* oops, this computer sucks! */
- {
- int i;
-
- fprintf (stderr,
- "%s: frame rate is only %.1f! "
- "Turning off pan/fade to compensate...\n",
- progname, fps);
- zoom = 100;
- fade_seconds = 0;
- ss->low_fps_p = True;
-
- sanity_check (mi);
-
- /* Reset all quads, and mark only #0 as active. */
- for (i = 0; i < countof(ss->quads); i++)
- {
- gls_quad *q = &ss->quads[i];
- q->state = DEAD;
- reset_quad (mi, q);
- q->texid = ss->current_texid;
- q->state = (i == 0 ? IN : DEAD);
- }
+ hack_resources();
- ss->pan_start_time = ss->now;
- ss->redisplay_needed_p = True;
+ ss->now = double_time();
+ ss->dawn_of_time = ss->now;
+ ss->prev_frame_time = ss->now;
- /* Need this in case zoom changed. */
- reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
- }
+ ss->awaiting_first_image_p = True;
+ alloc_image (mi);
}
-void
+ENTRYPOINT void
draw_slideshow (ModeInfo *mi)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
- Window w = MI_WINDOW(mi);
- double secs;
+ int i;
if (!ss->glx_context)
return;
- if (zoom < 100)
- ss->redisplay_needed_p = True;
+ glXMakeCurrent(MI_DISPLAY(mi), MI_WINDOW(mi), *(ss->glx_context));
- /* States:
- 0: - A invisible, B invisible
- - A fading in, B invisible
+ if (ss->awaiting_first_image_p)
+ {
+ image *img = ss->images[0];
+ if (!img) abort();
+ if (!img->loaded_p)
+ return;
- 1: - A opaque, B invisible
- - A fading out, B fading in
- - A invisible, gets reset
- - A invisible, B opaque
+ ss->awaiting_first_image_p = False;
+ ss->dawn_of_time = double_time();
- 2: - A invisible, B opaque
- - A fading in, B fading out
- - B invisible, gets reset
- - A opaque, B invisible (goto 1)
- */
+ /* start the very first sprite fading in */
+ new_sprite (mi);
+ }
ss->now = double_time();
- secs = ss->now - ss->pan_start_time;
+ /* Each sprite has three states: fading in, full, fading out.
+ The in/out states overlap like this:
- if (secs < fade_seconds)
- {
- /* We are in the midst of a fade:
- one quad is fading in, the other is fading out.
- (If this is the very first time, then the one
- fading out is already out.)
- */
- ss->redisplay_needed_p = True;
- ss->fade_frame_count++;
+ iiiiiiFFFFFFFFFFFFoooooo . . . . . . . . . . . . . . . . .
+ . . . . . . . . . iiiiiiFFFFFFFFFFFFoooooo . . . . . . . .
+ . . . . . . . . . . . . . . . . . . iiiiiiFFFFFFFFFFFFooooo
- if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
- (ss->quads[1].state == IN && ss->quads[0].state == OUT) ||
- (ss->quads[0].state == IN && ss->quads[1].state == DEAD)))
- abort();
- }
- else if (secs < pan_seconds)
+ So as soon as a sprite goes into the "out" state, we create
+ a new sprite (in the "in" state.)
+ */
+
+ if (ss->nsprites > 2) abort();
+
+ /* If a sprite is just entering the fade-out state,
+ then add a new sprite in the fade-in state.
+ */
+ for (i = 0; i < ss->nsprites; i++)
{
- /* One quad is visible and in motion, the other is not.
- */
- if (ss->fade_frame_count != 0) /* we just switched from fade to pan */
- ponder_state_change (mi);
- ss->pan_frame_count++;
+ sprite *sp = ss->sprites[i];
+ if (sp->state != sp->prev_state &&
+ sp->state == (fade_seconds == 0 ? DEAD : OUT))
+ new_sprite (mi);
}
- else
- {
- /* One quad is visible and in motion, the other is not.
- It's time to begin fading the visible one out, and the
- invisible one in. (Reset the invisible one first.)
- */
- gls_quad *vq, *iq;
- ponder_state_change (mi);
+ tick_sprites (mi);
- if (ss->quads[0].state == IN)
- {
- vq = &ss->quads[0];
- iq = &ss->quads[1];
- }
- else
+ /* Now garbage collect the dead sprites.
+ */
+ for (i = 0; i < ss->nsprites; i++)
+ {
+ sprite *sp = ss->sprites[i];
+ if (sp->state == DEAD)
{
- vq = &ss->quads[1];
- iq = &ss->quads[0];
+ destroy_sprite (mi, sp);
+ i--;
}
-
- if (vq->state != IN) abort();
-
- /* I don't understand why sometimes iq is still OUT and not DEAD. */
- if (iq->state == OUT) iq->state = DEAD;
- if (iq->state != DEAD) abort();
-
- vq->state = OUT;
-
- if (ss->image_start_time + image_seconds <= ss->now)
- load_quad (mi, iq);
-
- reset_quad (mi, iq); /* fade invisible in */
- iq->texid = ss->current_texid; /* make sure we're using latest img */
-
- ss->pan_start_time = ss->now;
-
- if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
- (ss->quads[1].state == IN && ss->quads[0].state == OUT)))
- abort();
}
- ss->fps = fps_1 (mi);
+ /* We can only ever end up with no sprites at all if the machine is
+ being really slow and we hopped states directly from FULL to DEAD
+ without passing OUT... */
+ if (ss->nsprites == 0)
+ new_sprite (mi);
if (!ss->redisplay_needed_p)
return;
- else if (debug_p && zoom == 100)
- fprintf (stderr, "%s: debug: drawing (%d)\n", progname,
- (int) (ss->now - ss->dawn_of_time));
- draw_quads (mi);
- ss->redisplay_needed_p = False;
+ if (debug_p && ss->now - ss->prev_frame_time > 1)
+ fprintf (stderr, "%s: static screen for %.1f secs\n",
+ blurb(), ss->now - ss->prev_frame_time);
+
+ draw_sprites (mi);
- if (mi->fps_p) fps_2(mi);
+ ss->fps = fps_1 (mi);
+ if (mi->fps_p) fps_2 (mi);
glFinish();
- glXSwapBuffers (MI_DISPLAY (mi), w);
+ glXSwapBuffers (MI_DISPLAY (mi), MI_WINDOW(mi));
+ ss->prev_frame_time = ss->now;
+ ss->redisplay_needed_p = False;
+ check_fps (mi);
}
+XSCREENSAVER_MODULE_2 ("GLSlideshow", glslideshow, slideshow)
+
#endif /* USE_GL */