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)
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));
267 #ifdef REFORMAT_IMAGE_DATA
269 /* Pulls the Pixmap bits from the server and returns an XImage
270 in some format acceptable to OpenGL.
273 pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
275 Display *dpy = DisplayOfScreen (screen);
276 Visual *visual = DefaultVisualOfScreen (screen);
277 unsigned int width, height, depth;
279 # ifdef HAVE_XSHM_EXTENSION
280 Bool use_shm = get_boolean_resource ("useSHM", "Boolean");
281 XShmSegmentInfo shm_info;
282 # endif /* HAVE_XSHM_EXTENSION */
284 XImage *server_ximage = 0;
285 XImage *client_ximage = 0;
291 XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
294 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
296 # ifdef HAVE_XSHM_EXTENSION
299 server_ximage = create_xshm_image (dpy, visual, depth,
300 ZPixmap, 0, &shm_info,
303 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
307 # endif /* HAVE_XSHM_EXTENSION */
310 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
312 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
314 # ifdef HAVE_XSHM_EXTENSION
316 destroy_xshm_image (dpy, server_ximage, &shm_info);
318 # endif /* HAVE_XSHM_EXTENSION */
319 XDestroyImage (server_ximage);
321 return client_ximage;
325 # else /* ! REFORMAT_IMAGE_DATA */
328 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
329 GLint type, format; /* ...use this. */
332 /* Abbreviate these so that the table entries all fit on one line...
334 #define BYTE GL_UNSIGNED_BYTE
335 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
336 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
337 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
338 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
339 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
340 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
341 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
342 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
343 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
344 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
345 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
346 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
348 static conversion_table ctable[] = {
349 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
350 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
351 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
352 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
353 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
354 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
355 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
356 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
357 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
358 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
359 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
360 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
361 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
362 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
363 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
364 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
365 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
366 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
367 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
368 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
369 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
370 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
371 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
372 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
373 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
374 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
378 /* Given an XImage, returns the GL settings to use its data as a texture.
381 gl_settings_for_ximage (XImage *image,
382 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
385 for (i = 0; i < countof(ctable); ++i)
387 if (image->bits_per_pixel == ctable[i].depth &&
388 image->red_mask == ctable[i].red_mask &&
389 image->green_mask == ctable[i].green_mask &&
390 image->blue_mask == ctable[i].blue_mask)
392 *type_ret = ctable[i].type;
393 *format_ret = ctable[i].format;
395 if (image->bits_per_pixel == 24)
397 /* don't know how to test this... */
398 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
403 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
408 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
410 (*format_ret == GL_RGB ? "RGB" :
411 *format_ret == GL_BGR ? "BGR" :
412 *format_ret == GL_RGBA ? "RGBA" :
413 *format_ret == GL_BGRA ? "BGRA" :
414 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
416 (*type_ret == BYTE ? "BYTE" :
417 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
418 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
419 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
420 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
421 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
422 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
423 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
424 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
425 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
426 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
427 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
428 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
431 image->bits_per_pixel,
432 image->red_mask, image->green_mask, image->blue_mask);
439 /* Unknown RGB fields? */
443 #endif /* ! REFORMAT_IMAGE_DATA */
447 int pix_width, pix_height, pix_depth;
452 /* Used in async mode
454 void (*callback) (const char *filename, XRectangle *geometry,
455 int iw, int ih, int tw, int th,
461 char **filename_return;
462 XRectangle *geometry_return;
463 int *image_width_return;
464 int *image_height_return;
465 int *texture_width_return;
466 int *texture_height_return;
471 /* Returns the current time in seconds as a double.
477 # ifdef GETTIMEOFDAY_TWO_ARGS
479 gettimeofday(&now, &tzp);
484 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
488 /* return the next larger power of 2. */
492 static unsigned int pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
493 2048, 4096, 8192, 16384, 32768, 65536 };
495 for (j = 0; j < countof(pow2); j++)
496 if (pow2[j] >= i) return pow2[j];
497 abort(); /* too big! */
501 /* Loads the given XImage into GL's texture memory.
502 The image may be of any size.
503 If mipmap_p is true, then make mipmaps instead of just a single texture.
504 Writes to stderr and returns False on error.
507 ximage_to_texture (XImage *ximage,
508 GLint type, GLint format,
513 int max_reduction = 7;
516 int orig_width = ximage->width;
517 int orig_height = ximage->height;
525 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
526 tex_width = ximage->width;
527 tex_height = ximage->height;
530 fprintf (stderr, "%s: mipmap %d x %d\n",
531 progname, ximage->width, ximage->height);
533 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
534 format, type, ximage->data);
539 /* glTexImage2D() requires the texture sizes to be powers of 2.
540 So first, create a texture of that size (but don't write any
543 tex_width = to_pow2 (ximage->width);
544 tex_height = to_pow2 (ximage->height);
547 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
548 progname, ximage->width, ximage->height,
549 tex_width, tex_height);
551 glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
555 /* Now load our non-power-of-2 image data into the existing texture. */
558 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
559 ximage->width, ximage->height,
560 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
568 const char *s = (char *) gluErrorString (err);
572 sprintf (buf, "unknown error %d", err);
576 while (glGetError() != GL_NO_ERROR)
577 ; /* clear any lingering errors */
579 if (++err_count > max_reduction)
583 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
584 "%s: The error was: \"%s\".\n"
585 "%s: probably this means "
586 "\"your video card is worthless and weak\"?\n\n",
587 progname, orig_width, orig_height,
588 ximage->width, ximage->height,
596 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
597 progname, ximage->width, ximage->height, s);
598 halve_image (ximage);
603 if (width_return) *width_return = tex_width;
604 if (height_return) *height_return = tex_height;
609 static void screen_to_texture_async_cb (Screen *screen,
610 Window window, Drawable drawable,
611 const char *name, XRectangle *geometry,
615 /* Grabs an image of the desktop (or another random image file) and
616 loads tht image into GL's texture memory.
617 Writes to stderr and returns False on error.
620 screen_to_texture (Screen *screen, Window window,
621 int desired_width, int desired_height,
623 char **filename_return,
624 XRectangle *geometry_return,
625 int *image_width_return,
626 int *image_height_return,
627 int *texture_width_return,
628 int *texture_height_return)
630 Display *dpy = DisplayOfScreen (screen);
631 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
632 XWindowAttributes xgwa;
634 XRectangle geom = { 0, 0, 0, 0 };
637 if (! image_width_return)
638 image_width_return = &wret;
641 data->load_time = double_time();
644 data->mipmap_p = mipmap_p;
645 data->filename_return = filename_return;
646 data->geometry_return = geometry_return;
647 data->image_width_return = image_width_return;
648 data->image_height_return = image_height_return;
649 data->texture_width_return = texture_width_return;
650 data->texture_height_return = texture_height_return;
652 XGetWindowAttributes (dpy, window, &xgwa);
653 data->pix_width = xgwa.width;
654 data->pix_height = xgwa.height;
655 data->pix_depth = xgwa.depth;
657 if (desired_width && desired_width < xgwa.width)
658 data->pix_width = desired_width;
659 if (desired_height && desired_height < xgwa.height)
660 data->pix_height = desired_height;
662 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
664 load_random_image (screen, window, data->pixmap, &filename, &geom);
665 screen_to_texture_async_cb (screen, window, data->pixmap, filename, &geom,
668 return (*image_width_return != 0);
672 /* Like the above, but the image is loaded in a background process,
673 and a callback is run when the loading is complete.
674 When the callback is called, the image data will have been loaded
675 into texture number `texid' (via glBindTexture.)
677 If an error occurred, width/height will be 0.
680 screen_to_texture_async (Screen *screen, Window window,
681 int desired_width, int desired_height,
684 void (*callback) (const char *filename,
685 XRectangle *geometry,
693 Display *dpy = DisplayOfScreen (screen);
694 XWindowAttributes xgwa;
695 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
698 data->load_time = double_time();
701 data->mipmap_p = mipmap_p;
702 data->callback = callback;
703 data->closure = closure;
705 XGetWindowAttributes (dpy, window, &xgwa);
706 data->pix_width = xgwa.width;
707 data->pix_height = xgwa.height;
708 data->pix_depth = xgwa.depth;
710 if (desired_width && desired_width < xgwa.width)
711 data->pix_width = desired_width;
712 if (desired_height && desired_height < xgwa.height)
713 data->pix_height = desired_height;
715 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
717 fork_load_random_image (screen, window, data->pixmap,
718 screen_to_texture_async_cb, data);
722 /* Once we have an XImage, this loads it into GL.
723 This is used in both synchronous and asynchronous mode.
726 screen_to_texture_async_cb (Screen *screen, Window window, Drawable drawable,
727 const char *name, XRectangle *geometry,
730 Display *dpy = DisplayOfScreen (screen);
734 int iw=0, ih=0, tw=0, th=0;
735 double cvt_time=0, tex_time=0, done_time=0;
736 img_closure *data = (img_closure *) closure;
737 /* copy closure data to stack and free the original before running cb */
738 img_closure dd = *data;
739 memset (data, 0, sizeof (*data));
743 if (geometry->width <= 0 || geometry->height <= 0)
745 /* This can happen if an old version of xscreensaver-getimage
749 geometry->width = dd.pix_width;
750 geometry->height = dd.pix_height;
753 if (geometry->width <= 0 || geometry->height <= 0)
757 cvt_time = double_time();
759 # ifdef REFORMAT_IMAGE_DATA
760 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
762 type = GL_UNSIGNED_BYTE;
764 #else /* ! REFORMAT_IMAGE_DATA */
766 Visual *visual = DefaultVisualOfScreen (screen);
769 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
770 dd.pix_width, dd.pix_height, 32, 0);
772 /* Note: height+2 in "to" to be to work around an array bounds overrun
773 in gluBuild2DMipmaps / gluScaleImage. */
774 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
777 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
778 ~0L, ximage->format, ximage, 0, 0))
780 XDestroyImage (ximage);
784 gl_settings_for_ximage (ximage, &type, &format, &swap);
785 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
787 #endif /* REFORMAT_IMAGE_DATA */
789 XFreePixmap (dpy, dd.pixmap);
793 tex_time = double_time();
802 glBindTexture (GL_TEXTURE_2D, dd.texid);
804 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
805 ok = ximage_to_texture (ximage, type, format, &tw, &th, dd.mipmap_p);
808 if (ximage) XDestroyImage (ximage);
811 iw = ih = tw = th = 0;
814 done_time = double_time();
818 /* prints: A + B + C = D
819 A = file I/O time (happens in background)
820 B = time to pull bits from server (this process)
821 C = time to convert bits to GL textures (this process)
822 D = total elapsed time from "want image" to "see image"
824 B+C is responsible for any frame-rate glitches.
826 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
828 cvt_time - dd.load_time,
830 done_time - tex_time,
831 done_time - dd.load_time);
834 /* asynchronous mode */
835 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
838 /* synchronous mode */
839 if (dd.filename_return) *dd.filename_return = (char *) name;
840 if (dd.geometry_return) *dd.geometry_return = *geometry;
841 if (dd.image_width_return) *dd.image_width_return = iw;
842 if (dd.image_height_return) *dd.image_height_return = ih;
843 if (dd.texture_width_return) *dd.texture_width_return = tw;
844 if (dd.texture_height_return) *dd.texture_height_return = th;