1 /* grab-ximage.c --- grab the screen to an XImage for use with OpenGL.
2 * xscreensaver, Copyright (c) 2001, 2003, 2005 Jamie Zawinski <jwz@jwz.org>
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that
7 * copyright notice and this permission notice appear in supporting
8 * documentation. No representations are made about the suitability of this
9 * software for any purpose. It is provided "as is" without express or
21 #include <X11/Xutil.h>
22 #include <GL/gl.h> /* only for GLfloat */
23 #include <GL/glu.h> /* for gluBuild2DMipmaps */
25 #include "grabscreen.h"
28 /* If REFORMAT_IMAGE_DATA is defined, then we convert Pixmaps to textures
31 - get Pixmap as an XImage in whatever form the server hands us;
32 - convert that XImage to 32-bit RGBA in client-local endianness;
33 - make the texture using RGBA, UNSIGNED_BYTE.
35 If undefined, we do this:
37 - get Pixmap as an XImage in whatever form the server hands us;
38 - figure out what OpenGL texture packing parameters correspond to
39 the image data that the server sent us and use that, e.g.,
40 BGRA, INT_8_8_8_8_REV.
42 You might expect the second method to be faster, since we're not making
43 a second copy of the data and iterating each pixel before we hand it
44 to GL. But, you'd be wrong. The first method is almost 6x faster.
45 I guess GL is reformatting it *again*, and doing it very inefficiently!
47 #define REFORMAT_IMAGE_DATA
50 #ifdef HAVE_XSHM_EXTENSION
51 # include "resources.h"
53 #endif /* HAVE_XSHM_EXTENSION */
55 extern char *progname;
57 #include <X11/Xutil.h>
61 #define MAX(a,b) ((a)>(b)?(a):(b))
64 #define countof(x) (sizeof((x))/sizeof((*x)))
67 static int debug_p = 0;
72 union { int i; char c[sizeof(int)]; } u;
78 #ifdef REFORMAT_IMAGE_DATA
80 /* Given a bitmask, returns the position and width of the field.
83 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
86 for (i = 0; i < 32; i++)
91 for (; i < 32; i++, j++)
92 if (! (mask & (1L << i)))
100 /* Given a value and a field-width, expands the field to fill out 8 bits.
103 spread_bits (unsigned char value, unsigned char width)
107 case 8: return value;
108 case 7: return (value << 1) | (value >> 6);
109 case 6: return (value << 2) | (value >> 4);
110 case 5: return (value << 3) | (value >> 2);
111 case 4: return (value << 4) | (value);
112 case 3: return (value << 5) | (value << 2) | (value >> 2);
113 case 2: return (value << 6) | (value << 4) | (value);
114 default: abort(); break;
120 convert_ximage_to_rgba32 (Screen *screen, XImage *image)
122 Display *dpy = DisplayOfScreen (screen);
123 Visual *visual = DefaultVisualOfScreen (screen);
126 unsigned int crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
127 unsigned int srpos=0, sgpos=0, sbpos=0;
128 unsigned int srmsk=0, sgmsk=0, sbmsk=0;
129 unsigned int srsiz=0, sgsiz=0, sbsiz=0;
132 unsigned char spread_map[3][256];
134 /* Note: height+2 in "to" to be to work around an array bounds overrun
135 in gluBuild2DMipmaps / gluScaleImage.
137 XImage *from = image;
138 XImage *to = XCreateImage (dpy, visual, 32, /* depth */
139 ZPixmap, 0, 0, from->width, from->height + 2,
142 to->data = (char *) calloc (to->height, to->bytes_per_line);
144 if (visual_class (screen, visual) == PseudoColor ||
145 visual_class (screen, visual) == GrayScale)
147 Colormap cmap = DefaultColormapOfScreen (screen);
148 int ncolors = visual_cells (screen, visual);
150 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
151 for (i = 0; i < ncolors; i++)
153 XQueryColors (dpy, cmap, colors, ncolors);
156 if (colors == 0) /* truecolor */
158 srmsk = to->red_mask;
159 sgmsk = to->green_mask;
160 sbmsk = to->blue_mask;
162 decode_mask (srmsk, &srpos, &srsiz);
163 decode_mask (sgmsk, &sgpos, &sgsiz);
164 decode_mask (sbmsk, &sbpos, &sbsiz);
167 /* Pack things in "RGBA" order in client endianness. */
169 crpos = 24, cgpos = 16, cbpos = 8, capos = 0;
171 crpos = 0, cgpos = 8, cbpos = 16, capos = 24;
173 if (colors == 0) /* truecolor */
175 for (i = 0; i < 256; i++)
177 spread_map[0][i] = spread_bits (i, srsiz);
178 spread_map[1][i] = spread_bits (i, sgsiz);
179 spread_map[2][i] = spread_bits (i, sbsiz);
183 for (y = 0; y < from->height; y++)
184 for (x = 0; x < from->width; x++)
186 unsigned long sp = XGetPixel (from, x, y);
187 unsigned char sr, sg, sb;
192 sr = colors[sp].red & 0xFF;
193 sg = colors[sp].green & 0xFF;
194 sb = colors[sp].blue & 0xFF;
198 sr = (sp & srmsk) >> srpos;
199 sg = (sp & sgmsk) >> sgpos;
200 sb = (sp & sbmsk) >> sbpos;
202 sr = spread_map[0][sr];
203 sg = spread_map[1][sg];
204 sb = spread_map[2][sb];
207 cp = ((sr << crpos) |
212 XPutPixel (to, x, y, cp);
215 if (colors) free (colors);
220 #endif /* REFORMAT_IMAGE_DATA */
222 /* Shrinks the XImage by a factor of two.
223 We use this when mipmapping fails on large textures.
226 halve_image (XImage *ximage, XRectangle *geom)
228 int w2 = ximage->width/2;
229 int h2 = ximage->height/2;
233 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
237 fprintf (stderr, "%s: shrinking image %dx%d -> %dx%d\n",
238 progname, ximage->width, ximage->height, w2, h2);
240 ximage2 = (XImage *) calloc (1, sizeof (*ximage2));
243 ximage2->height = h2;
244 ximage2->bytes_per_line = 0;
246 XInitImage (ximage2);
248 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
251 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
252 progname, ximage->width, ximage->height, w2, h2);
256 for (y = 0; y < h2; y++)
257 for (x = 0; x < w2; x++)
258 XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
275 #ifdef REFORMAT_IMAGE_DATA
277 /* Pulls the Pixmap bits from the server and returns an XImage
278 in some format acceptable to OpenGL.
281 pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
283 Display *dpy = DisplayOfScreen (screen);
284 Visual *visual = DefaultVisualOfScreen (screen);
285 unsigned int width, height, depth;
287 # ifdef HAVE_XSHM_EXTENSION
288 Bool use_shm = get_boolean_resource ("useSHM", "Boolean");
289 XShmSegmentInfo shm_info;
290 # endif /* HAVE_XSHM_EXTENSION */
292 XImage *server_ximage = 0;
293 XImage *client_ximage = 0;
299 XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
302 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
304 # ifdef HAVE_XSHM_EXTENSION
307 server_ximage = create_xshm_image (dpy, visual, depth,
308 ZPixmap, 0, &shm_info,
311 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
315 # endif /* HAVE_XSHM_EXTENSION */
318 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
320 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
322 # ifdef HAVE_XSHM_EXTENSION
324 destroy_xshm_image (dpy, server_ximage, &shm_info);
326 # endif /* HAVE_XSHM_EXTENSION */
327 XDestroyImage (server_ximage);
329 return client_ximage;
333 # else /* ! REFORMAT_IMAGE_DATA */
336 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
337 GLint type, format; /* ...use this. */
340 /* Abbreviate these so that the table entries all fit on one line...
342 #define BYTE GL_UNSIGNED_BYTE
343 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
344 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
345 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
346 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
347 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
348 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
349 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
350 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
351 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
352 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
353 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
354 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
356 static conversion_table ctable[] = {
357 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
358 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
359 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
360 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
361 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
362 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
363 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
364 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
365 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
366 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
367 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
368 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
369 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
370 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
371 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
372 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
373 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
374 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
375 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
376 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
377 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
378 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
379 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
380 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
381 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
382 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
386 /* Given an XImage, returns the GL settings to use its data as a texture.
389 gl_settings_for_ximage (XImage *image,
390 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
393 for (i = 0; i < countof(ctable); ++i)
395 if (image->bits_per_pixel == ctable[i].depth &&
396 image->red_mask == ctable[i].red_mask &&
397 image->green_mask == ctable[i].green_mask &&
398 image->blue_mask == ctable[i].blue_mask)
400 *type_ret = ctable[i].type;
401 *format_ret = ctable[i].format;
403 if (image->bits_per_pixel == 24)
405 /* don't know how to test this... */
406 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
411 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
416 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
418 (*format_ret == GL_RGB ? "RGB" :
419 *format_ret == GL_BGR ? "BGR" :
420 *format_ret == GL_RGBA ? "RGBA" :
421 *format_ret == GL_BGRA ? "BGRA" :
422 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
424 (*type_ret == BYTE ? "BYTE" :
425 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
426 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
427 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
428 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
429 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
430 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
431 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
432 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
433 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
434 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
435 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
436 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
439 image->bits_per_pixel,
440 image->red_mask, image->green_mask, image->blue_mask);
447 /* Unknown RGB fields? */
451 #endif /* ! REFORMAT_IMAGE_DATA */
455 int pix_width, pix_height, pix_depth;
460 /* Used in async mode
462 void (*callback) (const char *filename, XRectangle *geometry,
463 int iw, int ih, int tw, int th,
469 char **filename_return;
470 XRectangle *geometry_return;
471 int *image_width_return;
472 int *image_height_return;
473 int *texture_width_return;
474 int *texture_height_return;
479 /* Returns the current time in seconds as a double.
485 # ifdef GETTIMEOFDAY_TWO_ARGS
487 gettimeofday(&now, &tzp);
492 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
496 /* return the next larger power of 2. */
500 static unsigned int pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
501 2048, 4096, 8192, 16384, 32768, 65536 };
503 for (j = 0; j < countof(pow2); j++)
504 if (pow2[j] >= i) return pow2[j];
505 abort(); /* too big! */
509 /* Loads the given XImage into GL's texture memory.
510 The image may be of any size.
511 If mipmap_p is true, then make mipmaps instead of just a single texture.
512 Writes to stderr and returns False on error.
515 ximage_to_texture (XImage *ximage,
516 GLint type, GLint format,
519 XRectangle *geometry,
522 int max_reduction = 7;
525 int orig_width = ximage->width;
526 int orig_height = ximage->height;
534 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
535 tex_width = ximage->width;
536 tex_height = ximage->height;
539 fprintf (stderr, "%s: mipmap %d x %d\n",
540 progname, ximage->width, ximage->height);
542 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
543 format, type, ximage->data);
548 /* glTexImage2D() requires the texture sizes to be powers of 2.
549 So first, create a texture of that size (but don't write any
552 tex_width = to_pow2 (ximage->width);
553 tex_height = to_pow2 (ximage->height);
556 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
557 progname, ximage->width, ximage->height,
558 tex_width, tex_height);
560 glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
564 /* Now load our non-power-of-2 image data into the existing texture. */
567 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
568 ximage->width, ximage->height,
569 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
577 const char *s = (char *) gluErrorString (err);
581 sprintf (buf, "unknown error %d", err);
585 while (glGetError() != GL_NO_ERROR)
586 ; /* clear any lingering errors */
588 if (++err_count > max_reduction)
592 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
593 "%s: The error was: \"%s\".\n"
594 "%s: probably this means "
595 "\"your video card is worthless and weak\"?\n\n",
596 progname, orig_width, orig_height,
597 ximage->width, ximage->height,
605 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
606 progname, ximage->width, ximage->height, s);
607 halve_image (ximage, geometry);
612 if (width_return) *width_return = tex_width;
613 if (height_return) *height_return = tex_height;
618 static void screen_to_texture_async_cb (Screen *screen,
619 Window window, Drawable drawable,
620 const char *name, XRectangle *geometry,
624 /* Grabs an image of the desktop (or another random image file) and
625 loads tht image into GL's texture memory.
626 Writes to stderr and returns False on error.
629 screen_to_texture (Screen *screen, Window window,
630 int desired_width, int desired_height,
632 char **filename_return,
633 XRectangle *geometry_return,
634 int *image_width_return,
635 int *image_height_return,
636 int *texture_width_return,
637 int *texture_height_return)
639 Display *dpy = DisplayOfScreen (screen);
640 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
641 XWindowAttributes xgwa;
643 XRectangle geom = { 0, 0, 0, 0 };
646 if (! image_width_return)
647 image_width_return = &wret;
650 data->load_time = double_time();
653 data->mipmap_p = mipmap_p;
654 data->filename_return = filename_return;
655 data->geometry_return = geometry_return;
656 data->image_width_return = image_width_return;
657 data->image_height_return = image_height_return;
658 data->texture_width_return = texture_width_return;
659 data->texture_height_return = texture_height_return;
661 XGetWindowAttributes (dpy, window, &xgwa);
662 data->pix_width = xgwa.width;
663 data->pix_height = xgwa.height;
664 data->pix_depth = xgwa.depth;
666 if (desired_width && desired_width < xgwa.width)
667 data->pix_width = desired_width;
668 if (desired_height && desired_height < xgwa.height)
669 data->pix_height = desired_height;
671 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
673 load_random_image (screen, window, data->pixmap, &filename, &geom);
674 screen_to_texture_async_cb (screen, window, data->pixmap, filename, &geom,
677 return (*image_width_return != 0);
681 /* Like the above, but the image is loaded in a background process,
682 and a callback is run when the loading is complete.
683 When the callback is called, the image data will have been loaded
684 into texture number `texid' (via glBindTexture.)
686 If an error occurred, width/height will be 0.
689 screen_to_texture_async (Screen *screen, Window window,
690 int desired_width, int desired_height,
693 void (*callback) (const char *filename,
694 XRectangle *geometry,
702 Display *dpy = DisplayOfScreen (screen);
703 XWindowAttributes xgwa;
704 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
707 data->load_time = double_time();
710 data->mipmap_p = mipmap_p;
711 data->callback = callback;
712 data->closure = closure;
714 XGetWindowAttributes (dpy, window, &xgwa);
715 data->pix_width = xgwa.width;
716 data->pix_height = xgwa.height;
717 data->pix_depth = xgwa.depth;
719 if (desired_width && desired_width < xgwa.width)
720 data->pix_width = desired_width;
721 if (desired_height && desired_height < xgwa.height)
722 data->pix_height = desired_height;
724 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
726 fork_load_random_image (screen, window, data->pixmap,
727 screen_to_texture_async_cb, data);
731 /* Once we have an XImage, this loads it into GL.
732 This is used in both synchronous and asynchronous mode.
735 screen_to_texture_async_cb (Screen *screen, Window window, Drawable drawable,
736 const char *name, XRectangle *geometry,
739 Display *dpy = DisplayOfScreen (screen);
743 int iw=0, ih=0, tw=0, th=0;
744 double cvt_time=0, tex_time=0, done_time=0;
745 img_closure *data = (img_closure *) closure;
746 /* copy closure data to stack and free the original before running cb */
747 img_closure dd = *data;
748 memset (data, 0, sizeof (*data));
752 if (geometry->width <= 0 || geometry->height <= 0)
754 /* This can happen if an old version of xscreensaver-getimage
758 geometry->width = dd.pix_width;
759 geometry->height = dd.pix_height;
762 if (geometry->width <= 0 || geometry->height <= 0)
766 cvt_time = double_time();
768 # ifdef REFORMAT_IMAGE_DATA
769 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
771 type = GL_UNSIGNED_BYTE;
773 #else /* ! REFORMAT_IMAGE_DATA */
775 Visual *visual = DefaultVisualOfScreen (screen);
778 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
779 dd.pix_width, dd.pix_height, 32, 0);
781 /* Note: height+2 in "to" to be to work around an array bounds overrun
782 in gluBuild2DMipmaps / gluScaleImage. */
783 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
786 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
787 ~0L, ximage->format, ximage, 0, 0))
789 XDestroyImage (ximage);
793 gl_settings_for_ximage (ximage, &type, &format, &swap);
794 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
796 #endif /* REFORMAT_IMAGE_DATA */
798 XFreePixmap (dpy, dd.pixmap);
802 tex_time = double_time();
811 glBindTexture (GL_TEXTURE_2D, dd.texid);
813 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
814 ok = ximage_to_texture (ximage, type, format, &tw, &th, geometry,
818 iw = ximage->width; /* in case the image was shrunk */
823 if (ximage) XDestroyImage (ximage);
826 iw = ih = tw = th = 0;
829 done_time = double_time();
833 /* prints: A + B + C = D
834 A = file I/O time (happens in background)
835 B = time to pull bits from server (this process)
836 C = time to convert bits to GL textures (this process)
837 D = total elapsed time from "want image" to "see image"
839 B+C is responsible for any frame-rate glitches.
841 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
843 cvt_time - dd.load_time,
845 done_time - tex_time,
846 done_time - dd.load_time);
849 /* asynchronous mode */
850 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
853 /* synchronous mode */
854 if (dd.filename_return) *dd.filename_return = (char *) name;
855 if (dd.geometry_return) *dd.geometry_return = *geometry;
856 if (dd.image_width_return) *dd.image_width_return = iw;
857 if (dd.image_height_return) *dd.image_height_return = ih;
858 if (dd.texture_width_return) *dd.texture_width_return = tw;
859 if (dd.texture_height_return) *dd.texture_height_return = th;