X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=hacks%2Fglx%2Fglslideshow.c;h=d735e7bd5e3514010a9f41981587dfe45d4a3aa1;hp=f705a65c010da82bf24c30a26a6f4c23c2aba4cc;hb=ffd8c0873576a9e3065696a624dce6b766b77062;hpb=96bdd7cf6ea60c418a76921acaf0e34d6f5be930 diff --git a/hacks/glx/glslideshow.c b/hacks/glx/glslideshow.c index f705a65c..d735e7bd 100644 --- a/hacks/glx/glslideshow.c +++ b/hacks/glx/glslideshow.c @@ -1,4 +1,4 @@ -/* glslideshow, Copyright (c) 2003 Jamie Zawinski +/* glslideshow, Copyright (c) 2003, 2004 Jamie Zawinski * Loads a sequence of images and smoothly pans around them; crossfades * when loading new images. * @@ -15,6 +15,56 @@ * 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 @@ -33,6 +83,7 @@ # 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" \ @@ -45,6 +96,8 @@ "*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" @@ -69,7 +122,8 @@ 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 */ -} quad; + char *title; +} gls_quad; typedef struct { @@ -79,7 +133,7 @@ typedef struct { int motion_frames; /* how many frames each pan takes */ int fade_frames; /* how many frames fading in/out takes */ - quad quads[2]; /* the (up to) 2 quads we animate */ + gls_quad quads[2]; /* the (up to) 2 quads we animate */ GLuint texids[2]; /* textures: "old" and "new" */ GLuint current_texid; /* the "new" one */ @@ -100,6 +154,11 @@ typedef struct { 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; @@ -107,15 +166,18 @@ 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[] = { @@ -124,6 +186,8 @@ 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" }, }; @@ -134,11 +198,29 @@ static argtype vars[] = { { &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 @@ -157,7 +239,91 @@ double_time (void) static void -draw_quad (ModeInfo *mi, quad *q) +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) { slideshow_state *ss = &sss[MI_SCREEN(mi)]; int wire = MI_IS_WIREFRAME(mi); @@ -252,6 +418,20 @@ draw_quad (ModeInfo *mi, 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) @@ -316,7 +496,7 @@ draw_quads (ModeInfo *mi) /* Re-randomize the state of the given quad. */ static void -reset_quad (ModeInfo *mi, quad *q) +reset_quad (ModeInfo *mi, gls_quad *q) { /* slideshow_state *ss = &sss[MI_SCREEN(mi)];*/ @@ -378,7 +558,7 @@ shrink_image (ModeInfo *mi, XImage *ximage) 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, @@ -387,7 +567,7 @@ shrink_image (ModeInfo *mi, XImage *ximage) 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++) @@ -403,16 +583,17 @@ shrink_image (ModeInfo *mi, XImage *ximage) /* Load a new image into a texture for the given quad. */ static void -load_quad (ModeInfo *mi, 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. */ @@ -434,14 +615,24 @@ load_quad (ModeInfo *mi, quad *q) 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); @@ -466,7 +657,7 @@ load_quad (ModeInfo *mi, quad *q) if (status) { char buf[100]; - const char *s = gluErrorString (status); + const char *s = (char *) gluErrorString (status); if (!s || !*s) { @@ -484,17 +675,17 @@ load_quad (ModeInfo *mi, quad *q) "%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; } @@ -507,6 +698,24 @@ load_quad (ModeInfo *mi, quad *q) 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 @@ -521,6 +730,66 @@ load_quad (ModeInfo *mi, quad *q) } +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) @@ -560,7 +829,7 @@ glslideshow_handle_event (ModeInfo *mi, XEvent *event) 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; } @@ -597,6 +866,26 @@ sanity_check (ModeInfo *mi) } +/* 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) { @@ -618,11 +907,15 @@ 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) { @@ -642,23 +935,31 @@ init_slideshow (ModeInfo *mi) 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; for (i = 0; i < countof(ss->quads); i++) { - quad *q = &ss->quads[i]; + gls_quad *q = &ss->quads[i]; q->texid = ss->current_texid; q->state = DEAD; reset_quad (mi, q); 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 */ + } @@ -692,13 +993,14 @@ ponder_state_change (ModeInfo *mi) 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! */ @@ -706,9 +1008,8 @@ ponder_state_change (ModeInfo *mi) 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; @@ -718,7 +1019,7 @@ ponder_state_change (ModeInfo *mi) /* Reset all quads, and mark only #0 as active. */ for (i = 0; i < countof(ss->quads); i++) { - quad *q = &ss->quads[i]; + gls_quad *q = &ss->quads[i]; q->state = DEAD; reset_quad (mi, q); q->texid = ss->current_texid; @@ -795,7 +1096,7 @@ draw_slideshow (ModeInfo *mi) It's time to begin fading the visible one out, and the invisible one in. (Reset the invisible one first.) */ - quad *vq, *iq; + gls_quad *vq, *iq; ponder_state_change (mi); @@ -836,7 +1137,7 @@ draw_slideshow (ModeInfo *mi) 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);