-/*
- * glslideshow - takes a snapshot of the screen and smoothly scans around
- * in it
+/* 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.
*
- * Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
+ * First version Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
+ * based on flipscreen3d, Copyright (C) 2001 Ben Buxton (bb@cactii.net).
*
- * Framework based on flipscreen3d
- * Copyright (C) 2001 Ben Buxton (bb@cactii.net)
- *
- * Smooth transitions between multiple files added by
- * Jamie Zawinski <jwz@jwz.org>
+ * Almost entirely rewritten by jwz, 21-Jun-2003.
*
* 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:
+ *
+ * - 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 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 "True"
-# define DEF_DURATION "30"
-# define DEF_ZOOM "75"
-
-#define DEFAULTS "*delay: 20000 \n" \
- "*fade: " DEF_FADE "\n" \
- "*duration: " DEF_DURATION "\n" \
- "*zoom: " DEF_ZOOM "\n" \
- "*wireframe: False \n" \
- "*showFPS: False \n" \
- "*fpsSolid: True \n" \
- "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
+# 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_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" \
+ "*titles: " DEF_TITLES "\n" \
+ "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
+ "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
# include "xlockmore.h"
-#define RRAND(range) (random()%(range))
#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))
#include <stdlib.h>
#include "grab-ximage.h"
+typedef struct {
+ GLfloat x, y, w, h;
+} rect;
-#define QW 12.4 /* arbitrary size of the textured quads we render */
-#define QH 12.4
-#define QX -6.2
-#define QY 6.2
+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 */
+ char *title;
+} gls_quad;
-#define NQUADS 2 /* sometimes we draw 2 at once */
typedef struct {
GLXContext *glx_context;
- Window window;
+ time_t start_time; /* when we started displaying this image */
- int tw, th; /* texture width, height */
- GLfloat max_tx, max_ty;
+ int motion_frames; /* how many frames each pan takes */
+ int fade_frames; /* how many frames fading in/out takes */
- GLfloat qw, qh; /* q? are for the quad we'll draw */
- GLfloat qx[NQUADS], qy[NQUADS], qz[NQUADS];
- GLfloat dx[NQUADS], dy[NQUADS], dz[NQUADS];
+ gls_quad quads[2]; /* the (up to) 2 quads we animate */
+ GLuint texids[2]; /* textures: "old" and "new" */
+ GLuint current_texid; /* the "new" one */
- GLuint texids[NQUADS]; /* two textures: current img, incoming img */
+ int img_w, img_h; /* Size (pixels) of currently-loaded image */
- time_t start_time; /* when we started displaying this image */
+ 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 */
+
+ Bool redisplay_needed_p; /* Sometimes we can get away with not
+ re-painting. Tick this if a redisplay
+ is required. */
- int curr_screen;
- int curr_texid_index;
- int in_transition; /* true while we're drawing overlapping imgs */
- int in_file_transition; /* ...plus loading a new image */
- int frames; /* how many frames we've drawn in this pan */
+ 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
+ frame rate. */
+
+ Bool fork_p; /* threaded image loading; #### still buggy */
+
+ XFontStruct *xfont;
+ GLuint font_dlist;
} slideshow_state;
/* Command-line arguments
*/
-int fade; /* If true, transitions between pans (and between images) will
- be translucent; otherwise, they will be jump-cuts. */
-int duration; /* 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." */
-
-
-/* blah, apparently other magic numbers elsewhere in the file also
- affect this... can't just change these to speed up / slow down...
- */
-static int frames_per_pan = 300;
-static int frames_per_fade = 100;
+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[] = {
- {"+fade", ".slideshow.fade", XrmoptionNoArg, (caddr_t) "False" },
- {"-fade", ".slideshow.fade", XrmoptionNoArg, (caddr_t) "True" },
- {"-duration", ".slideshow.duration", XrmoptionSepArg, 0},
- {"-zoom", ".slideshow.zoom", XrmoptionSepArg, 0}
+ {"-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 },
+ {"-titles", ".slideshow.titles", XrmoptionNoArg, "True" },
+ {"+titles", ".slideshow.titles", XrmoptionNoArg, "True" },
+ {"-debug", ".slideshow.debug", XrmoptionNoArg, "True" },
};
static argtype vars[] = {
- {(caddr_t *) &fade, "fade", "Fade", DEF_FADE, t_Bool},
- {(caddr_t *) &duration, "duration", "Duration", DEF_DURATION, t_Int},
- {(caddr_t *) &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int}
+ { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
+ { &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},
+ { &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};
-/* draw the texture mapped quad.
- `screen' specifies which of the independently-moving images to draw.
+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
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
static void
-showscreen (ModeInfo *mi, int wire, int screen, int texid_index)
+load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
{
- slideshow_state *ss = &sss[MI_SCREEN(mi)];
- static GLfloat r = 1, g = 1, b = 1, a = 1;
- GLfloat qxw, qyh;
- GLfloat x, y, w, h;
+ const char *font = get_string_resource (res, "Font");
+ XFontStruct *f;
+ Font id;
+ int first, last;
+
+ if (!font) font = "-*-times-bold-r-normal-*-180-*";
- if (screen >= NQUADS) abort();
- qxw = ss->qx[screen] + ss->qw;
- qyh = ss->qy[screen] - ss->qh;
- x = ss->qx[screen];
- y = ss->qy[screen];
- w = qxw;
- h = qyh;
+ f = XLoadQueryFont(mi->dpy, font);
+ if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
- ss->qx[screen] += ss->dx[screen];
- ss->qy[screen] -= ss->dy[screen];
- ss->qz[screen] += ss->dz[screen];
+ 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");
- glTranslatef(0, 0, ss->qz[screen]);
+ *fontP = f;
+}
- if (ss->in_transition) {
- a = 1 - (ss->frames/100.0);
- if (screen != ss->curr_screen) {
- a = 1-a;
+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();
}
- else {
- a = 1;
- }
+ glPopAttrib();
- glColor4f(r, g, b, a);
+ glMatrixMode(GL_MODELVIEW);
+}
- if(!wire) {
- glEnable(GL_TEXTURE_2D);
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- glDepthMask(GL_FALSE);
- glBindTexture (GL_TEXTURE_2D, ss->texids[texid_index]);
- }
- glBegin(wire ? GL_LINE_LOOP : GL_QUADS);
+static void
+draw_quad (ModeInfo *mi, gls_quad *q)
+{
+ 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;
- glNormal3f(0, 0, 1);
+ if (q->state == DEAD)
+ return;
- glTexCoord2f(0, ss->max_ty);
- glVertex3f(x, y, 0);
+ secs = ss->now - ss->pan_start_time;
- glTexCoord2f(ss->max_tx, ss->max_ty);
- glVertex3f(w, y, 0);
+ if (q->state == OUT)
+ secs += pan_seconds;
- glTexCoord2f(ss->max_tx, 0);
- glVertex3f(w, h, 0);
+ ratio = secs / (pan_seconds + fade_seconds);
- glTexCoord2f(0, 0);
- glVertex3f(x, h, 0);
+ 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);
- glEnd();
+ if (secs < fade_seconds)
+ opacity = secs / (GLfloat) fade_seconds; /* fading in or out... */
+ else if (secs < pan_seconds)
+ opacity = 1; /* panning opaquely. */
+ else
+ opacity = 1 - ((secs - pan_seconds) /
+ (GLfloat) fade_seconds); /* fading in or out... */
- if (wire) {
- GLfloat i;
- int k = 10;
- glBegin(GL_LINES);
- for (i = x; i < w; i += ((w-x)/k))
- {
- glVertex3f(i, y, 0);
- glVertex3f(i, h, 0);
- }
- for (i = y; i >= h; i -= ((y-h)/k))
- {
- glVertex3f(x, i, 0);
- glVertex3f(w, i, 0);
- }
- glVertex3f(x, y, 0);
- glVertex3f(w, h, 0);
- glVertex3f(x, h, 0);
- glVertex3f(w, y, 0);
- glEnd();
- }
+ if (q->state == OUT && opacity < 0.0001)
+ q->state = DEAD;
- glDisable(GL_TEXTURE_2D);
- glDepthMask(GL_TRUE);
-
- glBegin(GL_LINE_LOOP);
- glVertex3f(x, y, 0);
- glVertex3f(x, h, 0);
- glVertex3f(w, h, 0);
- glVertex3f(w, y, 0);
- glEnd();
- glDisable(GL_BLEND);
-
- glTranslatef(0, 0, -ss->qz[screen]);
+ glPushMatrix();
+
+ glTranslatef (current.x, current.y, 0);
+ glScalef (current.w, current.h, 1);
+
+ 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();
+
+ glDisable (GL_TEXTURE_2D);
+ glDisable (GL_BLEND);
+ }
+
+ 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);
+
+
+ /* Draw a grid inside the box
+ */
+ if (wire)
+ {
+ 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();
+ }
+
+ 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)
+ {
+ /* Draw the "from" and "to" boxes
+ */
+ glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
+ (q->texid == ss->texids[0] ? 0 : opacity),
+ opacity);
+
+ 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();
+
+ 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();
+ }
}
static void
-display (ModeInfo *mi)
+draw_quads (ModeInfo *mi)
{
slideshow_state *ss = &sss[MI_SCREEN(mi)];
- int wire = MI_IS_WIREFRAME(mi);
- glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
- glLoadIdentity();
- gluLookAt(0, 0, 15,
- 0, 0, 0,
- 0, 1, 0);
+ GLfloat s, o;
+ int i;
+
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
glPushMatrix();
- showscreen (mi, wire, ss->curr_screen, ss->curr_texid_index);
+ s = (100.0 / zoom);
+ o = (1-s)/2;
+ glTranslatef (o, o, 0);
+ glScalef (s, s, s);
- if (ss->in_transition)
- showscreen (mi, wire, 1-ss->curr_screen,
- (ss->in_file_transition
- ? 1 - ss->curr_texid_index
- : ss->curr_texid_index));
+ for (i = 0; i < countof(ss->quads); i++)
+ draw_quad (mi, &ss->quads[i]);
glPopMatrix();
- glFlush();
-}
-void
-reshape_slideshow (ModeInfo *mi, int width, int height)
-{
- glViewport(0,0,(GLint)width, (GLint) height);
- glMatrixMode(GL_PROJECTION);
- glLoadIdentity();
- gluPerspective(45, 1, 2.0, 85);
- glMatrixMode(GL_MODELVIEW);
+ if (debug_p)
+ {
+ 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();
+ }
}
+
+/* Re-randomize the state of the given quad.
+ */
static void
-reset (ModeInfo *mi, int screen)
+reset_quad (ModeInfo *mi, gls_quad *q)
{
- slideshow_state *ss = &sss[MI_SCREEN(mi)];
- ss->frames = 0;
-
- if (screen >= NQUADS) abort();
- ss->dz[screen] = (-.02+(RRAND(400)/10000.0)) * (GLfloat) zoom/100.0;
+/* 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;
+
+ 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
- if (ss->dz[screen] < 0.0) {
- ss->qz[screen] = 6.0 + RRAND(300)/100.0;
- }
- else {
- ss->qz[screen] = 1.0 + RRAND(300)/100.0;
- }
+ if (q->state != DEAD)
+ abort(); /* we should only be resetting a quad when it's not visible. */
+
+ /* 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 (random() & 1)
+ {
+ q->from.w = small; q->from.h = small;
+ q->to.w = large; q->to.h = large;
+ }
+ else
+ {
+ q->from.w = large; q->from.h = large;
+ q->to.w = small; q->to.h = small;
+ }
- ss->qz[screen] *= (GLfloat) zoom/100.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);
- ss->dx[screen] = -.02 + RRAND(400)/10000.0;
- ss->dy[screen] =- .01 + RRAND(200)/10000.0;
+ q->state = IN;
+}
- ss->dx[screen] *= ss->qz[screen]/12.0;
- ss->dy[screen] *= ss->qz[screen]/12.0;
- ss->qx[screen] = QX - ss->dx[screen] * 40.0 * ss->qz[screen];
- ss->qy[screen] = QY + ss->dy[screen] * 40.0 * ss->qz[screen];
+/* Shrinks the XImage by a factor of two.
+ */
+static void
+shrink_image (ModeInfo *mi, XImage *ximage)
+{
+ int w2 = ximage->width/2;
+ int h2 = ximage->height/2;
+ int x, y;
+ XImage *ximage2;
+
+ if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
+ return;
+
+ if (debug_p)
+ fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
+ blurb(), 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)
+ {
+ fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
+ blurb(), ximage->width, ximage->height, w2, h2);
+ exit (1);
+ }
+ 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);
}
+/* Load a new image into a texture for the given quad.
+ */
static void
-getSnapshot (ModeInfo *mi, int into_texid)
+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;
-
- if(MI_IS_WIREFRAME(mi)) return;
+ 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(); */
+
+ /* Figure out which texid is currently in use, and pick the other one.
+ */
+ {
+ 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)
+ {
+ tid = ss->texids[i];
+ break;
+ }
+
+ if (tid == 0) abort(); /* both textures in use by visible quads? */
+ q->texid = tid;
+ ss->current_texid = tid;
+ }
- ss->qw = QW;
- ss->qh = QH;
+ 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);
- ss->tw = ximage->width;
- ss->th = ximage->height;
-
- ss->qw *= (GLfloat) ss->tw / MI_WIDTH(mi);
- ss->qh *= (GLfloat) ss->th / MI_HEIGHT(mi);
-
- ss->max_tx = (GLfloat) ss->tw / (GLfloat) ximage->width;
- ss->max_ty = (GLfloat) ss->th / (GLfloat) ximage->height;
+ if (q->title) /* strip filename to part after last /. */
+ {
+ char *s = strrchr (q->title, '/');
+ if (s) strcpy (q->title, s+1);
+ }
- glBindTexture (GL_TEXTURE_2D, into_texid);
+ if (debug_p)
+ {
+ fprintf (stderr, "%s: debug: loaded image %d: \"%s\"\n",
+ blurb(), q->texid, (q->title ? q->title : "(null)"));
+ load_time = double_time();
+ }
- glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
- GL_LINEAR_MIPMAP_LINEAR);
+ 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;
+
+ AGAIN:
+
clear_gl_error();
- status = gluBuild2DMipmaps(GL_TEXTURE_2D, 3,
- ximage->width, ximage->height,
- GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
+ 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) {
- const char *s = gluErrorString (status);
-
- fprintf(stderr, "%s: error mipmapping %dx%d texture: %s\n",
- progname, ximage->width, ximage->height,
- (s ? s : "(unknown)"));
- fprintf(stderr, "%s: turning on -wireframe.\n", progname);
- MI_IS_WIREFRAME(mi) = 1;
- clear_gl_error();
- }
+ if (status)
+ {
+ char buf[100];
+ const char *s = (char *) gluErrorString (status);
+
+ if (!s || !*s)
+ {
+ sprintf (buf, "unknown error %d", status);
+ s = buf;
+ }
+
+ clear_gl_error();
+
+ 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",
+ blurb(), MI_WIDTH(mi), MI_HEIGHT(mi),
+ ximage->width, ximage->height,
+ blurb(), s,
+ blurb());
+ exit (1);
+ }
+ else
+ {
+ if (debug_p)
+ fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
+ blurb(), ximage->width, ximage->height, s);
+ shrink_image (mi, ximage);
+ goto AGAIN;
+ }
+ }
check_gl_error("mipmapping"); /* should get a return code instead of a
GL error, but just in case... */
-
+
free(ximage->data);
ximage->data = 0;
XDestroyImage(ximage);
- ss->start_time = time ((time_t *) 0);
+ 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
+ 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;
+
+ ss->redisplay_needed_p = True;
}
+
+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)
+{
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ GLfloat s;
+ glViewport (0, 0, width, height);
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity();
+
+ s = 2;
+
+ if (debug_p)
+ {
+ s *= (zoom / 100.0) * 0.75;
+ if (s < 0.1) s = 0.1;
+ }
+
+ glScalef (s, s, s);
+ glTranslatef (-0.5, -0.5, 0);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ ss->redisplay_needed_p = True;
+}
+
+
+Bool
+glslideshow_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 (debug_p)
+ fprintf (stderr, "%s: debug: exposure\n", blurb());
+ ss->redisplay_needed_p = True;
+ return True;
+ }
+ return False;
+}
+
+
+/* Do some sanity checking on various user-supplied values, and make
+ sure they are all internally consistent.
+ */
+static void
+sanity_check (ModeInfo *mi)
+{
+ if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
+ else if (zoom > 100) zoom = 100;
+
+ if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
+ pan_seconds = fade_seconds;
+
+ if (pan_seconds == 0) /* no zero-length cycles, please... */
+ pan_seconds = 1;
+
+ if (image_seconds < pan_seconds) /* we only change images at fade-time */
+ image_seconds = pan_seconds;
+
+ /* If we're not panning/zooming within the image, then there's no point
+ in crossfading the image with itself -- only do crossfades when changing
+ to a new image. */
+ if (zoom == 100 && pan_seconds < image_seconds)
+ pan_seconds = image_seconds;
+
+ if (fps_cutoff < 0) fps_cutoff = 0;
+ else if (fps_cutoff > 30) fps_cutoff = 30;
+}
+
+
+/* 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)
{
int screen = MI_SCREEN(mi);
slideshow_state *ss;
+ int wire = MI_IS_WIREFRAME(mi);
+ int i;
- if(sss == NULL) {
- if((sss = (slideshow_state *)
- calloc(MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
+ if (sss == NULL) {
+ if ((sss = (slideshow_state *)
+ calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
return;
}
-
ss = &sss[screen];
- ss->window = MI_WINDOW(mi);
- ss->qw = QW;
- ss->qh = QH;
- if((ss->glx_context = init_GL(mi)) != NULL) {
- reshape_slideshow(mi, MI_WIDTH(mi), MI_HEIGHT(mi));
+ if ((ss->glx_context = init_GL(mi)) != NULL) {
+ reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
} else {
MI_CLEARWINDOW(mi);
}
- glClearColor(0.0,0.0,0.0,0.0);
-
- if(! MI_IS_WIREFRAME(mi)) {
- glShadeModel(GL_SMOOTH);
- glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
- glEnable(GL_DEPTH_TEST);
- glEnable(GL_CULL_FACE);
- glCullFace(GL_FRONT);
- glDisable(GL_LIGHTING);
-
- glGenTextures (1, &ss->texids[0]); /* texture for image A */
- glGenTextures (1, &ss->texids[1]); /* texture for image B */
- }
-
- reset(mi, ss->curr_screen);
- ss->curr_texid_index = 0;
- getSnapshot(mi, ss->texids[ss->curr_texid_index]);
-}
+ if (debug_p)
+ fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
+ blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
-void
-draw_slideshow (ModeInfo *mi)
-{
- slideshow_state *ss = &sss[MI_SCREEN(mi)];
- Window w = MI_WINDOW(mi);
- Display *disp = MI_DISPLAY(mi);
+ sanity_check(mi);
- if(!ss->glx_context) return;
+ if (debug_p)
+ fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
+ blurb(), pan_seconds, fade_seconds, image_seconds, zoom);
- glXMakeCurrent(disp, w, *(ss->glx_context));
-
- if (ss->frames == frames_per_pan) {
+ if (! wire)
+ {
+ glShadeModel (GL_SMOOTH);
+ glPolygonMode (GL_FRONT_AND_BACK,GL_FILL);
+ glEnable (GL_DEPTH_TEST);
+ glEnable (GL_CULL_FACE);
+ glCullFace (GL_FRONT);
+ glDisable (GL_LIGHTING);
+ }
- time_t now = time ((time_t *) 0);
+ ss->now = double_time ();
+ ss->dawn_of_time = ss->now;
- if(fade) {
- ss->in_transition = 1;
- reset (mi, 1 - ss->curr_screen);
+ if (debug_p) glLineWidth (3);
- if (ss->start_time + duration <= now) {
- ss->in_file_transition = 1;
- getSnapshot(mi, ss->texids[1 - ss->curr_texid_index]);
- }
+ ss->pan_start_time = ss->now;
+ ss->image_start_time = ss->now;
+
+ load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
- } else {
- reset(mi, ss->curr_screen);
+ for (i = 0; i < countof(ss->texids); i++)
+ glGenTextures (1, &ss->texids[i]);
+ ss->current_texid = 0;
- if (ss->start_time + duration <= now)
- getSnapshot(mi, ss->texids[ss->curr_texid_index]);
+ 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;
}
- }
- if (fade && ss->in_transition && ss->frames == frames_per_fade) {
- ss->in_transition = 0;
- ss->curr_screen = 1 - ss->curr_screen;
+ 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 */
+
+}
+
- if (ss->in_file_transition) {
- ss->in_file_transition = 0;
- ss->curr_texid_index = 1 - ss->curr_texid_index;
+/* 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
+ return; /* One of those should be non-zero! Maybe we just started,
+ and the machine is insanely slow. */
- display(mi);
-
- ss->frames++;
+ fps = frames / (GLfloat) secs;
- if(mi->fps_p) do_fps(mi);
+ if (debug_p)
+ fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
+ blurb(), which, frames, secs, fps);
- glFinish();
- glXSwapBuffers(disp, w);
+
+ if (fps < fps_cutoff && !ss->low_fps_p) /* oops, this computer sucks! */
+ {
+ int i;
+
+ fprintf (stderr,
+ "%s: only %.1f fps! Turning off pan/fade to compensate...\n",
+ blurb(), 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);
+ }
+
+ ss->pan_start_time = ss->now;
+ ss->redisplay_needed_p = True;
+
+ /* Need this in case zoom changed. */
+ reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
+ }
}
+
void
-release_slideshow (ModeInfo *mi)
+draw_slideshow (ModeInfo *mi)
{
- if(sss != NULL) {
- (void) free((void *) sss);
- sss = NULL;
- }
+ slideshow_state *ss = &sss[MI_SCREEN(mi)];
+ Window w = MI_WINDOW(mi);
+ double secs;
+
+ if (!ss->glx_context)
+ return;
+
+ if (zoom < 100)
+ ss->redisplay_needed_p = True;
+
+ /* States:
+ 0: - A invisible, B invisible
+ - A fading in, B invisible
+
+ 1: - A opaque, B invisible
+ - A fading out, B fading in
+ - A invisible, gets reset
+ - A invisible, B opaque
+
+ 2: - A invisible, B opaque
+ - A fading in, B fading out
+ - B invisible, gets reset
+ - A opaque, B invisible (goto 1)
+ */
+
+ ss->now = double_time();
+
+ secs = ss->now - ss->pan_start_time;
+
+ 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++;
+
+ 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)
+ {
+ /* 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++;
+ }
+ 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);
+
+ if (ss->quads[0].state == IN)
+ {
+ vq = &ss->quads[0];
+ iq = &ss->quads[1];
+ }
+ else
+ {
+ vq = &ss->quads[1];
+ iq = &ss->quads[0];
+ }
+
+ 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);
+
+ if (!ss->redisplay_needed_p)
+ return;
+ else if (debug_p && zoom == 100)
+ fprintf (stderr, "%s: debug: drawing (%d)\n", blurb(),
+ (int) (ss->now - ss->dawn_of_time));
- FreeAllGL(MI);
+ draw_quads (mi);
+ ss->redisplay_needed_p = False;
+
+ if (mi->fps_p) fps_2(mi);
+
+ glFinish();
+ glXSwapBuffers (MI_DISPLAY (mi), w);
}
-#endif
+#endif /* USE_GL */