1 /* grab-ximage.c --- grab the screen to an XImage for use with OpenGL.
2 * xscreensaver, Copyright (c) 2001-2008 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
23 # include <OpenGL/gl.h>
24 # include <OpenGL/glu.h>
26 # include <X11/Xlib.h>
27 # include <X11/Xutil.h>
28 # include <GL/gl.h> /* only for GLfloat */
29 # include <GL/glu.h> /* for gluBuild2DMipmaps */
30 # include <GL/glx.h> /* for glXMakeCurrent() */
33 #include "grab-ximage.h"
34 #include "grabscreen.h"
37 /* If REFORMAT_IMAGE_DATA is defined, then we convert Pixmaps to textures
40 - get Pixmap as an XImage in whatever form the server hands us;
41 - convert that XImage to 32-bit RGBA in client-local endianness;
42 - make the texture using RGBA, UNSIGNED_BYTE.
44 If undefined, we do this:
46 - get Pixmap as an XImage in whatever form the server hands us;
47 - figure out what OpenGL texture packing parameters correspond to
48 the image data that the server sent us and use that, e.g.,
49 BGRA, INT_8_8_8_8_REV.
51 You might expect the second method to be faster, since we're not making
52 a second copy of the data and iterating each pixel before we hand it
53 to GL. But, you'd be wrong. The first method is almost 6x faster.
54 I guess GL is reformatting it *again*, and doing it very inefficiently!
56 #define REFORMAT_IMAGE_DATA
59 #ifdef HAVE_XSHM_EXTENSION
60 # include "resources.h"
62 #endif /* HAVE_XSHM_EXTENSION */
64 extern char *progname;
71 # include <X11/Xutil.h>
75 #define MAX(a,b) ((a)>(b)?(a):(b))
78 #define countof(x) (sizeof((x))/sizeof((*x)))
81 static int debug_p = 0;
86 union { int i; char c[sizeof(int)]; } u;
92 #ifdef REFORMAT_IMAGE_DATA
94 /* Given a bitmask, returns the position and width of the field.
97 decode_mask (unsigned int mask, unsigned int *pos_ret, unsigned int *size_ret)
100 for (i = 0; i < 32; i++)
101 if (mask & (1L << i))
105 for (; i < 32; i++, j++)
106 if (! (mask & (1L << i)))
114 /* Given a value and a field-width, expands the field to fill out 8 bits.
117 spread_bits (unsigned char value, unsigned char width)
121 case 8: return value;
122 case 7: return (value << 1) | (value >> 6);
123 case 6: return (value << 2) | (value >> 4);
124 case 5: return (value << 3) | (value >> 2);
125 case 4: return (value << 4) | (value);
126 case 3: return (value << 5) | (value << 2) | (value >> 2);
127 case 2: return (value << 6) | (value << 4) | (value);
128 default: abort(); break;
134 convert_ximage_to_rgba32 (Screen *screen, XImage *image)
136 Display *dpy = DisplayOfScreen (screen);
137 Visual *visual = DefaultVisualOfScreen (screen);
140 unsigned int crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
141 unsigned int srpos=0, sgpos=0, sbpos=0;
142 unsigned int srmsk=0, sgmsk=0, sbmsk=0;
143 unsigned int srsiz=0, sgsiz=0, sbsiz=0;
146 unsigned char spread_map[3][256];
148 /* Note: height+2 in "to" to be to work around an array bounds overrun
149 in gluBuild2DMipmaps / gluScaleImage.
151 XImage *from = image;
152 XImage *to = XCreateImage (dpy, visual, 32, /* depth */
153 ZPixmap, 0, 0, from->width, from->height + 2,
156 to->data = (char *) calloc (to->height, to->bytes_per_line);
158 /* Set the bit order in the XImage structure to whatever the
159 local host's native bit order is.
161 to->bitmap_bit_order =
163 (bigendian() ? MSBFirst : LSBFirst);
165 if (visual_class (screen, visual) == PseudoColor ||
166 visual_class (screen, visual) == GrayScale)
168 Colormap cmap = DefaultColormapOfScreen (screen);
169 int ncolors = visual_cells (screen, visual);
171 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
172 for (i = 0; i < ncolors; i++)
174 XQueryColors (dpy, cmap, colors, ncolors);
177 if (colors == 0) /* truecolor */
179 srmsk = to->red_mask;
180 sgmsk = to->green_mask;
181 sbmsk = to->blue_mask;
183 decode_mask (srmsk, &srpos, &srsiz);
184 decode_mask (sgmsk, &sgpos, &sgsiz);
185 decode_mask (sbmsk, &sbpos, &sbsiz);
188 /* Pack things in "RGBA" order in client endianness. */
190 crpos = 24, cgpos = 16, cbpos = 8, capos = 0;
192 crpos = 0, cgpos = 8, cbpos = 16, capos = 24;
194 if (colors == 0) /* truecolor */
196 for (i = 0; i < 256; i++)
198 spread_map[0][i] = spread_bits (i, srsiz);
199 spread_map[1][i] = spread_bits (i, sgsiz);
200 spread_map[2][i] = spread_bits (i, sbsiz);
204 for (y = 0; y < from->height; y++)
205 for (x = 0; x < from->width; x++)
207 unsigned long sp = XGetPixel (from, x, y);
208 unsigned char sr, sg, sb;
213 sr = colors[sp].red & 0xFF;
214 sg = colors[sp].green & 0xFF;
215 sb = colors[sp].blue & 0xFF;
219 sr = (sp & srmsk) >> srpos;
220 sg = (sp & sgmsk) >> sgpos;
221 sb = (sp & sbmsk) >> sbpos;
223 sr = spread_map[0][sr];
224 sg = spread_map[1][sg];
225 sb = spread_map[2][sb];
228 cp = ((sr << crpos) |
233 XPutPixel (to, x, y, cp);
236 if (colors) free (colors);
241 #endif /* REFORMAT_IMAGE_DATA */
243 /* Shrinks the XImage by a factor of two.
244 We use this when mipmapping fails on large textures.
247 halve_image (XImage *ximage, XRectangle *geom)
249 int w2 = ximage->width/2;
250 int h2 = ximage->height/2;
254 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
258 fprintf (stderr, "%s: shrinking image %dx%d -> %dx%d\n",
259 progname, ximage->width, ximage->height, w2, h2);
261 ximage2 = (XImage *) calloc (1, sizeof (*ximage2));
264 ximage2->height = h2;
265 ximage2->bytes_per_line = 0;
267 XInitImage (ximage2);
269 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
272 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
273 progname, ximage->width, ximage->height, w2, h2);
277 for (y = 0; y < h2; y++)
278 for (x = 0; x < w2; x++)
279 XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
296 #ifdef REFORMAT_IMAGE_DATA
298 /* Pulls the Pixmap bits from the server and returns an XImage
299 in some format acceptable to OpenGL.
302 pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
304 Display *dpy = DisplayOfScreen (screen);
305 unsigned int width, height, depth;
307 # ifdef HAVE_XSHM_EXTENSION
308 Bool use_shm = get_boolean_resource (dpy, "useSHM", "Boolean");
309 XShmSegmentInfo shm_info;
310 # endif /* HAVE_XSHM_EXTENSION */
312 XImage *server_ximage = 0;
313 XImage *client_ximage = 0;
319 XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
322 if (width < 5 || height < 5) /* something's gone wrong somewhere... */
325 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
327 # ifdef HAVE_XSHM_EXTENSION
330 Visual *visual = DefaultVisualOfScreen (screen);
331 server_ximage = create_xshm_image (dpy, visual, depth,
332 ZPixmap, 0, &shm_info,
335 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
339 # endif /* HAVE_XSHM_EXTENSION */
342 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
344 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
346 # ifdef HAVE_XSHM_EXTENSION
348 destroy_xshm_image (dpy, server_ximage, &shm_info);
350 # endif /* HAVE_XSHM_EXTENSION */
351 XDestroyImage (server_ximage);
353 return client_ximage;
357 # else /* ! REFORMAT_IMAGE_DATA */
360 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
361 GLint type, format; /* ...use this. */
364 /* Abbreviate these so that the table entries all fit on one line...
366 #define BYTE GL_UNSIGNED_BYTE
367 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
368 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
369 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
370 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
371 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
372 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
373 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
374 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
375 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
376 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
377 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
378 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
380 static const conversion_table ctable[] = {
381 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
382 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
383 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
384 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
385 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
386 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
387 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
388 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
389 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
390 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
391 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
392 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
393 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
394 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
395 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
396 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
397 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
398 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
399 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
400 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
401 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
402 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
403 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
404 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
405 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
406 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
410 /* Given an XImage, returns the GL settings to use its data as a texture.
413 gl_settings_for_ximage (XImage *image,
414 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
417 for (i = 0; i < countof(ctable); ++i)
419 if (image->bits_per_pixel == ctable[i].depth &&
420 image->red_mask == ctable[i].red_mask &&
421 image->green_mask == ctable[i].green_mask &&
422 image->blue_mask == ctable[i].blue_mask)
424 *type_ret = ctable[i].type;
425 *format_ret = ctable[i].format;
427 if (image->bits_per_pixel == 24)
429 /* don't know how to test this... */
430 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
435 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
440 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
442 (*format_ret == GL_RGB ? "RGB" :
443 *format_ret == GL_BGR ? "BGR" :
444 *format_ret == GL_RGBA ? "RGBA" :
445 *format_ret == GL_BGRA ? "BGRA" :
446 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
448 (*type_ret == BYTE ? "BYTE" :
449 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
450 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
451 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
452 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
453 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
454 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
455 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
456 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
457 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
458 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
459 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
460 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
463 image->bits_per_pixel,
464 image->red_mask, image->green_mask, image->blue_mask);
471 /* Unknown RGB fields? */
475 #endif /* ! REFORMAT_IMAGE_DATA */
478 GLXContext glx_context;
480 int pix_width, pix_height, pix_depth;
485 /* Used in async mode
487 void (*callback) (const char *filename, XRectangle *geometry,
488 int iw, int ih, int tw, int th,
494 char **filename_return;
495 XRectangle *geometry_return;
496 int *image_width_return;
497 int *image_height_return;
498 int *texture_width_return;
499 int *texture_height_return;
504 /* Returns the current time in seconds as a double.
510 # ifdef GETTIMEOFDAY_TWO_ARGS
512 gettimeofday(&now, &tzp);
517 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
521 /* return the next larger power of 2. */
525 static const unsigned int pow2[] = {
526 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
527 2048, 4096, 8192, 16384, 32768, 65536 };
529 for (j = 0; j < countof(pow2); j++)
530 if (pow2[j] >= i) return pow2[j];
531 abort(); /* too big! */
535 /* Loads the given XImage into GL's texture memory.
536 The image may be of any size.
537 If mipmap_p is true, then make mipmaps instead of just a single texture.
538 Writes to stderr and returns False on error.
541 ximage_to_texture (XImage *ximage,
542 GLint type, GLint format,
545 XRectangle *geometry,
548 int max_reduction = 7;
551 int orig_width = ximage->width;
552 int orig_height = ximage->height;
560 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
561 tex_width = ximage->width;
562 tex_height = ximage->height;
565 fprintf (stderr, "%s: mipmap %d x %d\n",
566 progname, ximage->width, ximage->height);
568 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
569 format, type, ximage->data);
574 /* glTexImage2D() requires the texture sizes to be powers of 2.
575 So first, create a texture of that size (but don't write any
578 tex_width = to_pow2 (ximage->width);
579 tex_height = to_pow2 (ximage->height);
582 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
583 progname, ximage->width, ximage->height,
584 tex_width, tex_height);
586 glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
590 /* Now load our non-power-of-2 image data into the existing texture. */
593 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
594 ximage->width, ximage->height,
595 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
603 const char *s = (char *) gluErrorString (err);
607 sprintf (buf, "unknown error %d", (int) err);
611 while (glGetError() != GL_NO_ERROR)
612 ; /* clear any lingering errors */
614 if (++err_count > max_reduction)
618 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
619 "%s: The error was: \"%s\".\n"
620 "%s: probably this means "
621 "\"your video card is worthless and weak\"?\n\n",
622 progname, orig_width, orig_height,
623 ximage->width, ximage->height,
631 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
632 progname, ximage->width, ximage->height, s);
633 halve_image (ximage, geometry);
638 if (width_return) *width_return = tex_width;
639 if (height_return) *height_return = tex_height;
644 static void load_texture_async_cb (Screen *screen,
645 Window window, Drawable drawable,
646 const char *name, XRectangle *geometry,
650 /* Grabs an image of the desktop (or another random image file) and
651 loads the image into GL's texture memory.
652 When the callback is called, the image data will have been loaded
653 into texture number `texid' (via glBindTexture.)
655 If an error occurred, width/height will be 0.
658 load_texture_async (Screen *screen, Window window,
659 GLXContext glx_context,
660 int desired_width, int desired_height,
663 void (*callback) (const char *filename,
664 XRectangle *geometry,
672 Display *dpy = DisplayOfScreen (screen);
673 XWindowAttributes xgwa;
674 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
677 data->load_time = double_time();
680 data->mipmap_p = mipmap_p;
681 data->glx_context = glx_context;
682 data->callback = callback;
683 data->closure = closure;
685 XGetWindowAttributes (dpy, window, &xgwa);
686 data->pix_width = xgwa.width;
687 data->pix_height = xgwa.height;
688 data->pix_depth = xgwa.depth;
690 if (desired_width && desired_width < xgwa.width)
691 data->pix_width = desired_width;
692 if (desired_height && desired_height < xgwa.height)
693 data->pix_height = desired_height;
695 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
697 load_image_async (screen, window, data->pixmap,
698 load_texture_async_cb, data);
702 /* Once we have an XImage, this loads it into GL.
703 This is used in both synchronous and asynchronous mode.
706 load_texture_async_cb (Screen *screen, Window window, Drawable drawable,
707 const char *name, XRectangle *geometry, void *closure)
709 Display *dpy = DisplayOfScreen (screen);
713 int iw=0, ih=0, tw=0, th=0;
714 double cvt_time=0, tex_time=0, done_time=0;
715 img_closure *data = (img_closure *) closure;
716 /* copy closure data to stack and free the original before running cb */
717 img_closure dd = *data;
718 memset (data, 0, sizeof (*data));
723 glXMakeCurrent (dpy, window, dd.glx_context);
725 if (geometry->width <= 0 || geometry->height <= 0)
727 /* This can happen if an old version of xscreensaver-getimage
731 geometry->width = dd.pix_width;
732 geometry->height = dd.pix_height;
735 if (geometry->width <= 0 || geometry->height <= 0)
739 cvt_time = double_time();
741 # ifdef REFORMAT_IMAGE_DATA
742 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
744 type = GL_UNSIGNED_BYTE;
746 #else /* ! REFORMAT_IMAGE_DATA */
748 Visual *visual = DefaultVisualOfScreen (screen);
751 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
752 dd.pix_width, dd.pix_height, 32, 0);
754 /* Note: height+2 in "to" to be to work around an array bounds overrun
755 in gluBuild2DMipmaps / gluScaleImage. */
756 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
759 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
760 ~0L, ximage->format, ximage, 0, 0))
762 XDestroyImage (ximage);
766 gl_settings_for_ximage (ximage, &type, &format, &swap);
767 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
769 #endif /* REFORMAT_IMAGE_DATA */
771 XFreePixmap (dpy, dd.pixmap);
775 tex_time = double_time();
784 glBindTexture (GL_TEXTURE_2D, dd.texid);
786 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
787 ok = ximage_to_texture (ximage, type, format, &tw, &th, geometry,
791 iw = ximage->width; /* in case the image was shrunk */
796 if (ximage) XDestroyImage (ximage);
799 iw = ih = tw = th = 0;
802 done_time = double_time();
806 /* prints: A + B + C = D
807 A = file I/O time (happens in background)
808 B = time to pull bits from server (this process)
809 C = time to convert bits to GL textures (this process)
810 D = total elapsed time from "want image" to "see image"
812 B+C is responsible for any frame-rate glitches.
814 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
816 cvt_time - dd.load_time,
818 done_time - tex_time,
819 done_time - dd.load_time);
822 /* asynchronous mode */
823 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
826 /* synchronous mode */
827 if (dd.filename_return) *dd.filename_return = (char *) name;
828 if (dd.geometry_return) *dd.geometry_return = *geometry;
829 if (dd.image_width_return) *dd.image_width_return = iw;
830 if (dd.image_height_return) *dd.image_height_return = ih;
831 if (dd.texture_width_return) *dd.texture_width_return = tw;
832 if (dd.texture_height_return) *dd.texture_height_return = th;