1 /* glslideshow, Copyright (c) 2003 Jamie Zawinski <jwz@jwz.org>
2 * Loads a sequence of images and smoothly pans around them; crossfades
3 * when loading new images.
5 * First version Copyright (c) 2002, 2003 Mike Oliphant (oliphant@gtk.org)
6 * based on flipscreen3d, Copyright (C) 2001 Ben Buxton (bb@cactii.net).
8 * Almost entirely rewritten by jwz, 21-Jun-2003.
10 * Permission to use, copy, modify, distribute, and sell this software and its
11 * documentation for any purpose is hereby granted without fee, provided that
12 * the above copyright notice appear in all copies and that both that
13 * copyright notice and this permission notice appear in supporting
14 * documentation. No representations are made about the suitability of this
15 * software for any purpose. It is provided "as is" without express or
20 #include <X11/Intrinsic.h>
23 # define PROGCLASS "GLSlideshow"
24 # define HACK_INIT init_slideshow
25 # define HACK_DRAW draw_slideshow
26 # define HACK_RESHAPE reshape_slideshow
27 # define HACK_HANDLE_EVENT glslideshow_handle_event
28 # define EVENT_MASK (ExposureMask|VisibilityChangeMask)
29 # define slideshow_opts xlockmore_opts
31 # define DEF_FADE_DURATION "2"
32 # define DEF_PAN_DURATION "6"
33 # define DEF_IMAGE_DURATION "30"
34 # define DEF_ZOOM "75"
35 # define DEF_FPS_CUTOFF "5"
36 # define DEF_TITLES "False"
37 # define DEF_DEBUG "False"
39 #define DEFAULTS "*delay: 20000 \n" \
40 "*fadeDuration: " DEF_FADE_DURATION "\n" \
41 "*panDuration: " DEF_PAN_DURATION "\n" \
42 "*imageDuration: " DEF_IMAGE_DURATION "\n" \
43 "*zoom: " DEF_ZOOM "\n" \
44 "*FPScutoff: " DEF_FPS_CUTOFF "\n" \
45 "*debug : " DEF_DEBUG "\n" \
46 "*wireframe: False \n" \
47 "*showFPS: False \n" \
48 "*fpsSolid: True \n" \
49 "*titles: " DEF_TITLES "\n" \
50 "*titleFont: -*-times-bold-r-normal-*-180-*\n" \
51 "*desktopGrabber: xscreensaver-getimage -no-desktop %s\n"
53 # include "xlockmore.h"
56 #define countof(x) (sizeof((x))/sizeof((*x)))
65 #include "grab-ximage.h"
72 GLuint texid; /* which texture to draw */
73 enum { IN, OUT, DEAD } state; /* how to draw it */
74 rect from, to; /* the journey this quad is taking */
80 GLXContext *glx_context;
81 time_t start_time; /* when we started displaying this image */
83 int motion_frames; /* how many frames each pan takes */
84 int fade_frames; /* how many frames fading in/out takes */
86 gls_quad quads[2]; /* the (up to) 2 quads we animate */
87 GLuint texids[2]; /* textures: "old" and "new" */
88 GLuint current_texid; /* the "new" one */
90 int img_w, img_h; /* Size (pixels) of currently-loaded image */
92 double now; /* current time in seconds */
93 double pan_start_time; /* when this pan began */
94 double image_start_time; /* when this image was loaded */
95 double dawn_of_time; /* when the program launched */
97 Bool redisplay_needed_p; /* Sometimes we can get away with not
98 re-painting. Tick this if a redisplay
101 GLfloat fps; /* approximate frame rate we're achieving */
102 int pan_frame_count; /* More frame-rate stats */
103 int fade_frame_count;
104 Bool low_fps_p; /* Whether we have compensated for a low
112 static slideshow_state *sss = NULL;
115 /* Command-line arguments
117 static int fade_seconds; /* Duration in seconds of fade transitions.
118 If 0, jump-cut instead of fading. */
119 static int pan_seconds; /* Duration of each pan through an image. */
120 static int image_seconds; /* How many seconds until loading a new image. */
121 static int zoom; /* How far in to zoom when panning, in percent of
122 image size: that is, 75 means "when zoomed all
123 the way in, 75% of the image will be visible."
125 static int fps_cutoff; /* If the frame-rate falls below this, turn off
127 static Bool do_titles; /* Display image titles. */
128 static Bool debug_p; /* Be loud and do weird things. */
131 static XrmOptionDescRec opts[] = {
132 {"-fade", ".slideshow.fadeDuration", XrmoptionSepArg, 0 },
133 {"-pan", ".slideshow.panDuration", XrmoptionSepArg, 0 },
134 {"-duration", ".slideshow.imageDuration", XrmoptionSepArg, 0 },
135 {"-zoom", ".slideshow.zoom", XrmoptionSepArg, 0 },
136 {"-cutoff", ".slideshow.FPScutoff", XrmoptionSepArg, 0 },
137 {"-titles", ".slideshow.titles", XrmoptionNoArg, "True" },
138 {"+titles", ".slideshow.titles", XrmoptionNoArg, "True" },
139 {"-debug", ".slideshow.debug", XrmoptionNoArg, "True" },
142 static argtype vars[] = {
143 { &fade_seconds, "fadeDuration", "FadeDuration", DEF_FADE_DURATION, t_Int},
144 { &pan_seconds, "panDuration", "PanDuration", DEF_PAN_DURATION, t_Int},
145 { &image_seconds, "imageDuration","ImageDuration",DEF_IMAGE_DURATION, t_Int},
146 { &zoom, "zoom", "Zoom", DEF_ZOOM, t_Int},
147 { &fps_cutoff, "FPScutoff", "FPSCutoff", DEF_FPS_CUTOFF, t_Int},
148 { &debug_p, "debug", "Debug", DEF_DEBUG, t_Bool},
149 { &do_titles, "titles", "Titles", DEF_TITLES, t_Bool},
152 ModeSpecOpt slideshow_opts = {countof(opts), opts, countof(vars), vars, NULL};
155 /* Returns the current time in seconds as a double.
161 # ifdef GETTIMEOFDAY_TWO_ARGS
163 gettimeofday(&now, &tzp);
168 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
173 load_font (ModeInfo *mi, char *res, XFontStruct **fontP, GLuint *dlistP)
175 const char *font = get_string_resource (res, "Font");
180 if (!font) font = "-*-times-bold-r-normal-*-180-*";
182 f = XLoadQueryFont(mi->dpy, font);
183 if (!f) f = XLoadQueryFont(mi->dpy, "fixed");
186 first = f->min_char_or_byte2;
187 last = f->max_char_or_byte2;
190 *dlistP = glGenLists ((GLuint) last+1);
191 check_gl_error ("glGenLists");
192 glXUseXFont(id, first, last-first+1, *dlistP + first);
193 check_gl_error ("glXUseXFont");
200 print_title_string (ModeInfo *mi, const char *string, GLfloat x, GLfloat y)
202 slideshow_state *ss = &sss[MI_SCREEN(mi)];
203 XFontStruct *font = ss->xfont;
204 GLfloat line_height = font->ascent + font->descent;
208 glPushAttrib (GL_TRANSFORM_BIT | /* for matrix contents */
209 GL_ENABLE_BIT); /* for various glDisable calls */
210 glDisable (GL_LIGHTING);
211 glDisable (GL_DEPTH_TEST);
213 glMatrixMode(GL_PROJECTION);
218 glMatrixMode(GL_MODELVIEW);
225 gluOrtho2D (0, mi->xgwa.width, 0, mi->xgwa.height);
227 glRasterPos2f (x, y);
228 for (i = 0; i < strlen(string); i++)
233 glRasterPos2f (x, (y -= line_height));
238 glCallList (ss->font_dlist + (int)(c));
239 x2 += (font->per_char
240 ? font->per_char[c - font->min_char_or_byte2].width
241 : font->min_bounds.width);
247 glMatrixMode(GL_PROJECTION);
252 glMatrixMode(GL_MODELVIEW);
257 draw_quad (ModeInfo *mi, gls_quad *q)
259 slideshow_state *ss = &sss[MI_SCREEN(mi)];
260 int wire = MI_IS_WIREFRAME(mi);
268 if (q->state == DEAD)
271 secs = ss->now - ss->pan_start_time;
276 ratio = secs / (pan_seconds + fade_seconds);
278 current.x = q->from.x + ratio * (q->to.x - q->from.x);
279 current.y = q->from.y + ratio * (q->to.y - q->from.y);
280 current.w = q->from.w + ratio * (q->to.w - q->from.w);
281 current.h = q->from.h + ratio * (q->to.h - q->from.h);
283 if (secs < fade_seconds)
284 opacity = secs / (GLfloat) fade_seconds; /* fading in or out... */
285 else if (secs < pan_seconds)
286 opacity = 1; /* panning opaquely. */
288 opacity = 1 - ((secs - pan_seconds) /
289 (GLfloat) fade_seconds); /* fading in or out... */
291 if (q->state == OUT && opacity < 0.0001)
296 glTranslatef (current.x, current.y, 0);
297 glScalef (current.w, current.h, 1);
301 texw = mi->xgwa.width / (GLfloat) ss->img_w;
302 texh = mi->xgwa.height / (GLfloat) ss->img_h;
304 glEnable (GL_TEXTURE_2D);
306 glBindTexture (GL_TEXTURE_2D, q->texid);
307 glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
308 glDepthMask (GL_FALSE);
310 /* Draw the texture quad
312 glColor4f (1, 1, 1, opacity);
313 glNormal3f (0, 0, 1);
315 glTexCoord2f (0, 0); glVertex3f (0, 0, 0);
316 glTexCoord2f (0, texh); glVertex3f (0, 1, 0);
317 glTexCoord2f (texw, texh); glVertex3f (1, 1, 0);
318 glTexCoord2f (texw, 0); glVertex3f (1, 0, 0);
321 glDisable (GL_TEXTURE_2D);
322 glDisable (GL_BLEND);
326 glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
327 (q->texid == ss->texids[0] ? 0 : opacity),
330 glColor4f (1, 1, 1, opacity);
333 /* Draw a grid inside the box
340 glVertex3f (0, 0, 0); glVertex3f (1, 1, 0);
341 glVertex3f (1, 0, 0); glVertex3f (0, 1, 0);
343 for (y = 0; y < 1+d; y += d)
344 for (x = 0; x < 1+d; x += d)
346 glVertex3f (0, y, 0); glVertex3f (1, y, 0);
347 glVertex3f (x, 0, 0); glVertex3f (x, 1, 0);
354 q->title && *q->title)
356 /* #### this is wrong -- I really want to draw this with
357 "1,1,1,opacity", so that the text gets laid down on top
358 of the image with alpha, but that doesn't work, and I
361 glColor4f (opacity, opacity, opacity, 1);
362 print_title_string (mi, q->title,
363 10, mi->xgwa.height - 10);
370 /* Draw the "from" and "to" boxes
372 glColor4f ((q->texid == ss->texids[0] ? opacity : 0), 0,
373 (q->texid == ss->texids[0] ? 0 : opacity),
376 glBegin (GL_LINE_LOOP);
377 glVertex3f (q->from.x, q->from.y, 0);
378 glVertex3f (q->from.x + q->from.w, q->from.y, 0);
379 glVertex3f (q->from.x + q->from.w, q->from.y + q->from.h, 0);
380 glVertex3f (q->from.x, q->from.y + q->from.h, 0);
383 glBegin (GL_LINE_LOOP);
384 glVertex3f (q->to.x, q->to.y, 0);
385 glVertex3f (q->to.x + q->to.w, q->to.y, 0);
386 glVertex3f (q->to.x + q->to.w, q->to.y + q->to.h, 0);
387 glVertex3f (q->to.x, q->to.y + q->to.h, 0);
394 draw_quads (ModeInfo *mi)
396 slideshow_state *ss = &sss[MI_SCREEN(mi)];
400 glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
406 glTranslatef (o, o, 0);
409 for (i = 0; i < countof(ss->quads); i++)
410 draw_quad (mi, &ss->quads[i]);
416 glColor4f (1, 1, 1, 1);
417 glBegin (GL_LINE_LOOP);
418 glVertex3f (0, 0, 0);
419 glVertex3f (0, 1, 0);
420 glVertex3f (1, 1, 0);
421 glVertex3f (1, 0, 0);
427 /* Re-randomize the state of the given quad.
430 reset_quad (ModeInfo *mi, gls_quad *q)
432 /* slideshow_state *ss = &sss[MI_SCREEN(mi)];*/
434 GLfloat mid_w = (zoom / 100.0);
435 GLfloat mid_h = (zoom / 100.0);
436 GLfloat mid_x = (1 - mid_w) / 2;
437 GLfloat mid_y = (1 - mid_h) / 2;
439 GLfloat small = mid_w + frand ((1 - mid_w) * 0.3);
441 GLfloat large = small + frand ((1 - small) / 2) + ((1 - small) / 2);
443 GLfloat large = small + frand (1 - small);
446 if (q->state != DEAD)
447 abort(); /* we should only be resetting a quad when it's not visible. */
449 /* Possible box sizes range between "zoom" and "100%".
450 Pick a small box size, and a large box size.
451 Assign each a random position within the 1x1 box,
452 such that they encompass the middle "zoom" percentage.
453 One of those is the start, and one is the end.
454 Each frame will transition between one and the other.
459 q->from.w = small; q->from.h = small;
460 q->to.w = large; q->to.h = large;
464 q->from.w = large; q->from.h = large;
465 q->to.w = small; q->to.h = small;
468 q->from.x = mid_x - frand (q->from.w - mid_w);
469 q->from.y = mid_y - frand (q->from.h - mid_h);
470 q->to.x = mid_x - frand (q->to.w - mid_w);
471 q->to.y = mid_y - frand (q->to.w - mid_h);
477 /* Shrinks the XImage by a factor of two.
480 shrink_image (ModeInfo *mi, XImage *ximage)
482 int w2 = ximage->width/2;
483 int h2 = ximage->height/2;
487 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
491 fprintf (stderr, "%s: debug: shrinking image %dx%d -> %dx%d\n",
492 progname, ximage->width, ximage->height, w2, h2);
494 ximage2 = XCreateImage (MI_DISPLAY (mi), mi->xgwa.visual,
497 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
500 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
501 progname, ximage->width, ximage->height, w2, h2);
504 for (y = 0; y < h2; y++)
505 for (x = 0; x < w2; x++)
506 XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
514 /* Load a new image into a texture for the given quad.
517 load_quad (ModeInfo *mi, gls_quad *q)
519 slideshow_state *ss = &sss[MI_SCREEN(mi)];
522 int max_reduction = 7;
524 int wire = MI_IS_WIREFRAME(mi);
526 if (q->state != DEAD) abort();
528 /* Figure out which texid is currently in use, and pick the other one.
533 if (ss->current_texid == 0)
536 for (i = 0; i < countof(ss->texids); i++)
537 if (ss->texids[i] != ss->current_texid)
543 if (tid == 0) abort(); /* both textures in use by visible quads? */
545 ss->current_texid = tid;
549 fprintf (stderr, "%s: debug: loading image %d (%dx%d)\n",
550 progname, q->texid, mi->xgwa.width, mi->xgwa.height);
555 if (q->title) free (q->title);
557 ximage = screen_to_ximage (mi->xgwa.screen, mi->window, &q->title);
559 if (q->title) /* strip filename to part after last /. */
561 char *s = strrchr (q->title, '/');
562 if (s) strcpy (q->title, s+1);
566 fprintf (stderr, "%s: debug: loaded image %d (%s)\n",
567 progname, q->texid, (q->title ? q->title : "(null)"));
569 glBindTexture (GL_TEXTURE_2D, q->texid);
570 glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
571 glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
572 GL_LINEAR_MIPMAP_LINEAR);
574 ss->img_w = ximage->width;
575 ss->img_h = ximage->height;
580 status = gluBuild2DMipmaps (GL_TEXTURE_2D, 3,
581 ximage->width, ximage->height,
582 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
584 if(!status && glGetError())
585 /* Some implementations of gluBuild2DMipmaps(), but set a GL error anyway.
586 We could just call check_gl_error(), but that would exit. */
592 const char *s = (char *) gluErrorString (status);
596 sprintf (buf, "unknown error %d", status);
602 if (++err_count > max_reduction)
606 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
607 "%s: GLU said: \"%s\".\n"
608 "%s: probably this means "
609 "\"your video card is worthless and weak\"?\n\n",
610 progname, MI_WIDTH(mi), MI_HEIGHT(mi),
611 ximage->width, ximage->height,
619 fprintf (stderr, "%s: debug: mipmap error (%dx%d): %s\n",
620 progname, ximage->width, ximage->height, s);
621 shrink_image (mi, ximage);
626 check_gl_error("mipmapping"); /* should get a return code instead of a
627 GL error, but just in case... */
631 XDestroyImage(ximage);
635 /* Re-set "now" so that time spent loading the image file does not count
636 against the time remaining in this stage of the animation: image loading,
637 if it takes a perceptible amount of time, will cause the animation to
638 pause, but will not cause it to drop frames.
640 ss->now = double_time ();
641 ss->image_start_time = ss->now;
643 ss->redisplay_needed_p = True;
648 reshape_slideshow (ModeInfo *mi, int width, int height)
650 slideshow_state *ss = &sss[MI_SCREEN(mi)];
652 glViewport (0, 0, width, height);
653 glMatrixMode (GL_PROJECTION);
655 glMatrixMode (GL_MODELVIEW);
662 s *= (zoom / 100.0) * 0.75;
663 if (s < 0.1) s = 0.1;
667 glTranslatef (-0.5, -0.5, 0);
669 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
671 ss->redisplay_needed_p = True;
676 glslideshow_handle_event (ModeInfo *mi, XEvent *event)
678 slideshow_state *ss = &sss[MI_SCREEN(mi)];
680 if (event->xany.type == Expose ||
681 event->xany.type == GraphicsExpose ||
682 event->xany.type == VisibilityNotify)
685 fprintf (stderr, "%s: debug: exposure\n", progname);
686 ss->redisplay_needed_p = True;
693 /* Do some sanity checking on various user-supplied values, and make
694 sure they are all internally consistent.
697 sanity_check (ModeInfo *mi)
699 if (zoom < 1) zoom = 1; /* zoom is a positive percentage */
700 else if (zoom > 100) zoom = 100;
702 if (pan_seconds < fade_seconds) /* pan is inclusive of fade */
703 pan_seconds = fade_seconds;
705 if (pan_seconds == 0) /* no zero-length cycles, please... */
708 if (image_seconds < pan_seconds) /* we only change images at fade-time */
709 image_seconds = pan_seconds;
711 /* If we're not panning/zooming within the image, then there's no point
712 in crossfading the image with itself -- only do crossfades when changing
714 if (zoom == 100 && pan_seconds < image_seconds)
715 pan_seconds = image_seconds;
717 if (fps_cutoff < 0) fps_cutoff = 0;
718 else if (fps_cutoff > 30) fps_cutoff = 30;
723 init_slideshow (ModeInfo *mi)
725 int screen = MI_SCREEN(mi);
727 int wire = MI_IS_WIREFRAME(mi);
731 if ((sss = (slideshow_state *)
732 calloc (MI_NUM_SCREENS(mi), sizeof(slideshow_state))) == NULL)
737 if ((ss->glx_context = init_GL(mi)) != NULL) {
738 reshape_slideshow (mi, MI_WIDTH(mi), MI_HEIGHT(mi));
746 fprintf (stderr, "%s: debug: pan: %d; fade: %d; img: %d; zoom: %d%%\n",
747 progname, pan_seconds, fade_seconds, image_seconds, zoom);
751 glShadeModel (GL_SMOOTH);
752 glPolygonMode (GL_FRONT_AND_BACK,GL_FILL);
753 glEnable (GL_DEPTH_TEST);
754 glEnable (GL_CULL_FACE);
755 glCullFace (GL_FRONT);
756 glDisable (GL_LIGHTING);
759 ss->now = double_time ();
760 ss->dawn_of_time = ss->now;
762 if (debug_p) glLineWidth (3);
764 ss->pan_start_time = ss->now;
765 ss->image_start_time = ss->now;
767 load_font (mi, "titleFont", &ss->xfont, &ss->font_dlist);
769 for (i = 0; i < countof(ss->texids); i++)
770 glGenTextures (1, &ss->texids[i]);
771 ss->current_texid = 0;
773 for (i = 0; i < countof(ss->quads); i++)
775 gls_quad *q = &ss->quads[i];
776 q->texid = ss->current_texid;
782 load_quad (mi, &ss->quads[0]);
783 ss->quads[0].state = IN;
785 ss->redisplay_needed_p = True;
789 /* Call this each time we change from one state to another.
790 It gathers statistics on the frame rate of the previous state,
791 and if it's bad, turn things off (under the assumption that
792 we're running on sucky hardware.)
795 ponder_state_change (ModeInfo *mi)
797 slideshow_state *ss = &sss[MI_SCREEN(mi)];
802 if (ss->fade_frame_count && ss->pan_frame_count)
803 abort(); /* one of these should be zero! */
804 else if (ss->fade_frame_count) /* just finished fading */
808 frames = ss->fade_frame_count;
809 ss->fade_frame_count = 0;
811 else if (ss->pan_frame_count) /* just finished panning */
815 frames = ss->pan_frame_count;
816 ss->pan_frame_count = 0;
819 abort(); /* one of these should be non-zero! */
821 fps = frames / (GLfloat) secs;
824 fprintf (stderr, "%s: debug: %s %3d frames %2d sec %4.1f fps\n",
825 progname, which, frames, secs, fps);
828 if (fps < fps_cutoff && !ss->low_fps_p) /* oops, this computer sucks! */
833 "%s: frame rate is only %.1f! "
834 "Turning off pan/fade to compensate...\n",
838 ss->low_fps_p = True;
842 /* Reset all quads, and mark only #0 as active. */
843 for (i = 0; i < countof(ss->quads); i++)
845 gls_quad *q = &ss->quads[i];
848 q->texid = ss->current_texid;
849 q->state = (i == 0 ? IN : DEAD);
852 ss->pan_start_time = ss->now;
853 ss->redisplay_needed_p = True;
855 /* Need this in case zoom changed. */
856 reshape_slideshow (mi, mi->xgwa.width, mi->xgwa.height);
862 draw_slideshow (ModeInfo *mi)
864 slideshow_state *ss = &sss[MI_SCREEN(mi)];
865 Window w = MI_WINDOW(mi);
868 if (!ss->glx_context)
872 ss->redisplay_needed_p = True;
875 0: - A invisible, B invisible
876 - A fading in, B invisible
878 1: - A opaque, B invisible
879 - A fading out, B fading in
880 - A invisible, gets reset
881 - A invisible, B opaque
883 2: - A invisible, B opaque
884 - A fading in, B fading out
885 - B invisible, gets reset
886 - A opaque, B invisible (goto 1)
889 ss->now = double_time();
891 secs = ss->now - ss->pan_start_time;
893 if (secs < fade_seconds)
895 /* We are in the midst of a fade:
896 one quad is fading in, the other is fading out.
897 (If this is the very first time, then the one
898 fading out is already out.)
900 ss->redisplay_needed_p = True;
901 ss->fade_frame_count++;
903 if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
904 (ss->quads[1].state == IN && ss->quads[0].state == OUT) ||
905 (ss->quads[0].state == IN && ss->quads[1].state == DEAD)))
908 else if (secs < pan_seconds)
910 /* One quad is visible and in motion, the other is not.
912 if (ss->fade_frame_count != 0) /* we just switched from fade to pan */
913 ponder_state_change (mi);
914 ss->pan_frame_count++;
918 /* One quad is visible and in motion, the other is not.
919 It's time to begin fading the visible one out, and the
920 invisible one in. (Reset the invisible one first.)
924 ponder_state_change (mi);
926 if (ss->quads[0].state == IN)
937 if (vq->state != IN) abort();
939 /* I don't understand why sometimes iq is still OUT and not DEAD. */
940 if (iq->state == OUT) iq->state = DEAD;
941 if (iq->state != DEAD) abort();
945 if (ss->image_start_time + image_seconds <= ss->now)
948 reset_quad (mi, iq); /* fade invisible in */
949 iq->texid = ss->current_texid; /* make sure we're using latest img */
951 ss->pan_start_time = ss->now;
953 if (! ((ss->quads[0].state == IN && ss->quads[1].state == OUT) ||
954 (ss->quads[1].state == IN && ss->quads[0].state == OUT)))
958 ss->fps = fps_1 (mi);
960 if (!ss->redisplay_needed_p)
962 else if (debug_p && zoom == 100)
963 fprintf (stderr, "%s: debug: drawing (%d)\n", progname,
964 (int) (ss->now - ss->dawn_of_time));
967 ss->redisplay_needed_p = False;
969 if (mi->fps_p) fps_2(mi);
972 glXSwapBuffers (MI_DISPLAY (mi), w);