-/* glslideshow, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
+/* glslideshow, Copyright (c) 2003, 2004 Jamie Zawinski <jwz@jwz.org>
* Loads a sequence of images and smoothly pans around them; crossfades
* when loading new images.
*
* software for any purpose. It is provided "as is" without express or
* implied warranty.
*
+ * TODO:
+ *
+ * - Resizing the window makes everything go black forevermore. No idea why.
+ *
+ *
+ * - 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 less than half a
+ * second.) On slower machines, it can be much more pronounced.
+ *
+ * In xscreensaver 4.17, I added the new functions fork_load_random_image()
+ * and fork_screen_to_ximage() to make it possible to do image loading in
+ * the background, in an attempt to solve this (the idea being to only swap
+ * in the new image once it has been loaded.) Using those routines, we
+ * continue animating while the file system is being searched for an image
+ * file; while that image data is read, parsed, and decompressed; while that
+ * data is placed on a Pixmap in the X server.
+ *
+ * However, two things still happen in the "parent" (glslideshow) process:
+ * converting that server-side Pixmap to a client-side XImage (XGetImage);
+ * and converting that XImage to an OpenGL texture (gluBuild2DMipmaps).
+ * It's possible that some new code would allow us to do the Pixmap-to-XImage
+ * conversion in the forked process (feed it back upstream through a pipe or
+ * SHM segment or something); however, it turns out that significant
+ * parent-process image-loading time is being spent in gluBuild2DMipmaps().
+ *
+ * So, the next step would be to figure out some way to create a texture on
+ * the other end of the fork that would be usable by the parent process. Is
+ * that even possible? Is it possible to use a single GLX context in a
+ * multithreaded way like that? (Or use a second GLX context, but allow the
+ * two contexts to share data?)
+ *
+ * Another question remains: is the stalling happening in the GL/GLX
+ * libraries, or are we actually seeing a stall on the graphics pipeline?
+ * If the latter, then no amount of threading would help, because the
+ * bottleneck is pushing the bits from system memory to the graphics card.
+ *
+ * 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?
+ *
+ *
+ * - Even if the glitch was solved, there's still a bug in the background
+ * loading of images: as soon as the image comes in, we slap it into place
+ * in the target quad. This can lead to an image being changed while it is
+ * still being drawn, if that quad happens to be visible already. Instead,
+ * when the callback goes off, we should make sure to load it into the
+ * invisible quad, or if both are visible, we should wait until one goes
+ * invisible and then load it there (in other words, wait for the next
+ * fade-out to end.)
*/
#include <X11/Intrinsic.h>
# define DEF_IMAGE_DURATION "30"
# define DEF_ZOOM "75"
# define DEF_FPS_CUTOFF "5"
+# define DEF_TITLES "False"
# define DEF_DEBUG "False"
#define DEFAULTS "*delay: 20000 \n" \
"*wireframe: False \n" \
"*showFPS: False \n" \
"*fpsSolid: True \n" \
+ "*titles: " DEF_TITLES "\n" \
+ "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
"*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
# include "xlockmore.h"
GLuint texid; /* which texture to draw */
enum { IN, OUT, DEAD } state; /* how to draw it */
rect from, to; /* the journey this quad is taking */
+ char *title;
} gls_quad;
Bool low_fps_p; /* Whether we have compensated for a low
frame rate. */
+ Bool fork_p; /* threaded image loading; #### still buggy */
+
+ XFontStruct *xfont;
+ GLuint font_dlist;
+
} 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 do_titles; /* Display image titles. */
+static Bool debug_p; /* Be loud and do weird things. */
static XrmOptionDescRec opts[] = {
{"-duration", ".slideshow.imageDuration", XrmoptionSepArg, 0 },
{"-zoom", ".slideshow.zoom", XrmoptionSepArg, 0 },
{"-cutoff", ".slideshow.FPScutoff", XrmoptionSepArg, 0 },
+ {"-titles", ".slideshow.titles", XrmoptionNoArg, "True" },
+ {"+titles", ".slideshow.titles", XrmoptionNoArg, "True" },
{"-debug", ".slideshow.debug", XrmoptionNoArg, "True" },
};
{ &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
{ &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};
+static const char *
+blurb (void)
+{
+ 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;
+}
+
+
/* Returns the current time in seconds as a double.
*/
static double
}
+static void
+load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
+{
+ const char *font = get_string_resource (res, "Font");
+ XFontStruct *f;
+ Font id;
+ int first, last;
+
+ if (!font) font = "-*-times-bold-r-normal-*-180-*";
+
+ f = XLoadQueryFont(mi->dpy, font);
+ if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
+
+ id = f->fid;
+ first = f->min_char_or_byte2;
+ last = f->max_char_or_byte2;
+
+ clear_gl_error ();
+ *dlistP = glGenLists ((GLuint) last+1);
+ check_gl_error ("glGenLists");
+ glXUseXFont(id, first, last-first+1, *dlistP + first);
+ check_gl_error ("glXUseXFont");
+
+ *fontP = f;
+}
+
+
+static void
+print_title_string (ModeInfo *mi, const char *string, GLfloat x, GLfloat y)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ XFontStruct *font = ss->xfont;
+ GLfloat line_height = font->ascent + font->descent;
+
+ y -= line_height;
+
+ glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
+ GL_ENABLE_BIT); /* for various glDisable calls */
+ glDisable (GL_LIGHTING);
+ glDisable (GL_DEPTH_TEST);
+ {
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ {
+ glLoadIdentity();
+
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ {
+ unsigned int i;
+ int x2 = x;
+ glLoadIdentity();
+
+ gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
+
+ glRasterPos2f (x, y);
+ for (i = 0; i < strlen(string); i++)
+ {
+ char c = string[i];
+ if (c == '\n')
+ {
+ glRasterPos2f (x, (y -= line_height));
+ x2 = x;
+ }
+ else
+ {
+ glCallList (ss->font_dlist + (int)(c));
+ x2 += (font->per_char
+ ? font->per_char[c - font->min_char_or_byte2].width
+ : font->min_bounds.width);
+ }
+ }
+ }
+ glPopMatrix();
+ }
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ }
+ glPopAttrib();
+
+ glMatrixMode(GL_MODELVIEW);
+}
+
+
static void
draw_quad (ModeInfo *mi, gls_quad *q)
{
glEnd();
}
+ if (do_titles &&
+ q->state != DEAD &&
+ q->title && *q->title)
+ {
+ /* #### this is wrong -- I really want to draw this with
+ "1,1,1,opacity", so that the text gets laid down on top
+ of the image with alpha, but that doesn't work, and I
+ don't know why...
+ */
+ glColor4f (opacity, opacity, opacity, 1);
+ print_title_string (mi, q->title,
+ 10, mi->xgwa.height - 10);
+ }
+
glPopMatrix();
if (debug_p)
if (debug_p)
fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
- progname, ximage->width, ximage->height, w2, h2);
+ blurb(), ximage->width, ximage->height, w2, h2);
ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
32, ZPixmap, 0, 0,
if (!ximage2->data)
{
fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
- progname, ximage->width, ximage->height, w2, h2);
+ blurb(), ximage->width, ximage->height, w2, h2);
exit (1);
}
for (y = 0; y < h2; y++)
/* Load a new image into a texture for the given quad.
*/
static void
-load_quad (ModeInfo *mi, gls_quad *q)
+load_quad_1 (ModeInfo *mi, gls_quad *q, XImage *ximage,
+ const char *filename, double start_time, double cvt_time)
{
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);
+ double load_time=0, mipmap_time=0; /* for debugging messages */
- if (q->state != DEAD) abort();
+ /* if (q->state != DEAD) abort(); */
/* Figure out which texid is currently in use, and pick the other one.
*/
ss->current_texid = tid;
}
- if (debug_p)
- fprintf (stderr, "%s: debug: loading image %d (%dx%d)\n",
- progname, q->texid, mi->xgwa.width, mi->xgwa.height);
-
if (wire)
goto DONE;
- ximage = screen_to_ximage (mi->xgwa.screen, mi->window);
+ if (q->title) free (q->title);
+ q->title = (filename ? strdup (filename) : 0);
+
+ if (q->title) /* strip filename to part after last /. */
+ {
+ char *s = strrchr (q->title, '/');
+ if (s) strcpy (q->title, s+1);
+ }
+
+ if (debug_p)
+ {
+ fprintf (stderr, "%s: debug: loaded image %d: \"%s\"\n",
+ blurb(), q->texid, (q->title ? q->title : "(null)"));
+ load_time = double_time();
+ }
glBindTexture (GL_TEXTURE_2D, q->texid);
glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
"%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),
+ blurb(), MI_WIDTH(mi), MI_HEIGHT(mi),
ximage->width, ximage->height,
- progname, s,
- progname);
+ blurb(), s,
+ blurb());
exit (1);
}
else
{
if (debug_p)
fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
- progname, ximage->width, ximage->height, s);
+ blurb(), ximage->width, ximage->height, s);
shrink_image (mi, ximage);
goto AGAIN;
}
ximage->data = 0;
XDestroyImage(ximage);
+ if (debug_p)
+ {
+ fprintf (stderr, "%s: debug: mipmapped image %d: %dx%d\n",
+ blurb(), q->texid, mi->xgwa.width, mi->xgwa.height);
+ mipmap_time = double_time();
+ }
+
+ if (cvt_time == 0)
+ cvt_time = load_time;
+ if (debug_p)
+ fprintf (stderr,
+ "%s: debug: load time elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
+ blurb(),
+ cvt_time - start_time,
+ load_time - cvt_time,
+ mipmap_time - load_time,
+ mipmap_time - start_time);
+
DONE:
/* Re-set "now" so that time spent loading the image file does not count
}
+static void slideshow_load_cb (Screen *, Window, XImage *,
+ const char *filename, void *closure,
+ double cvt_time);
+
+typedef struct {
+ ModeInfo *mi;
+ gls_quad *q;
+ double start_time;
+} img_load_closure;
+
+
+/* Load a new image into a texture for the given quad.
+ */
+static void
+load_quad (ModeInfo *mi, gls_quad *q)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ img_load_closure *data;
+
+ if (debug_p)
+ fprintf (stderr, "%s: debug: loading image %d: %dx%d\n",
+ blurb(), q->texid, mi->xgwa.width, mi->xgwa.height);
+
+ if (q->state != DEAD) abort();
+ if (q->title) free (q->title);
+ q->title = 0;
+
+ if (MI_IS_WIREFRAME(mi))
+ return;
+
+ data = (img_load_closure *) calloc (1, sizeof(*data));
+ data->mi = mi;
+ data->q = q;
+ data->start_time = double_time();
+
+ if (ss->fork_p)
+ {
+ fork_screen_to_ximage (mi->xgwa.screen, mi->window,
+ slideshow_load_cb, data);
+ }
+ else
+ {
+ char *title = 0;
+ XImage *ximage = screen_to_ximage (mi->xgwa.screen, mi->window, &title);
+ slideshow_load_cb (mi->xgwa.screen, mi->window, ximage, title, data, 0);
+ }
+}
+
+
+static void
+slideshow_load_cb (Screen *screen, Window window, XImage *ximage,
+ const char *filename, void *closure, double cvt_time)
+{
+ img_load_closure *data = (img_load_closure *) closure;
+ load_quad_1 (data->mi, data->q, ximage, filename,
+ data->start_time, cvt_time);
+ memset (data, 0, sizeof (*data));
+ free (data);
+}
+
void
reshape_slideshow (ModeInfo *mi, int width, int height)
event->xany.type == VisibilityNotify)
{
if (debug_p)
- fprintf (stderr, "%s: debug: exposure\n", progname);
+ fprintf (stderr, "%s: debug: exposure\n", blurb());
ss->redisplay_needed_p = True;
return True;
}
}
+/* 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
+}
+
+
void
init_slideshow (ModeInfo *mi)
{
MI_CLEARWINDOW(mi);
}
+ if (debug_p)
+ fprintf (stderr, "%s: debug: 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);
+ blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
if (! wire)
{
ss->pan_start_time = ss->now;
ss->image_start_time = ss->now;
+ load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
+
for (i = 0; i < countof(ss->texids); i++)
glGenTextures (1, &ss->texids[i]);
ss->current_texid = 0;
q->state = DEAD;
}
+ if (debug_p)
+ hack_resources();
+
load_quad (mi, &ss->quads[0]);
ss->quads[0].state = IN;
ss->redisplay_needed_p = True;
+
+ ss->fork_p = 0; /* #### buggy */
+
}
ss->pan_frame_count = 0;
}
else
- abort(); /* one of these should be non-zero! */
+ return; /* One of those should be non-zero! Maybe we just started,
+ and the machine is insanely slow. */
fps = frames / (GLfloat) secs;
if (debug_p)
fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
- progname, which, frames, secs, fps);
+ blurb(), 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);
+ "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
+ blurb(), fps);
zoom = 100;
fade_seconds = 0;
ss->low_fps_p = True;
if (!ss->redisplay_needed_p)
return;
else if (debug_p && zoom == 100)
- fprintf (stderr, "%s: debug: drawing (%d)\n", progname,
+ fprintf (stderr, "%s: debug: drawing (%d)\n", blurb(),
(int) (ss->now - ss->dawn_of_time));
draw_quads (mi);