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;
145 unsigned char spread_map[3][256];
147 /* Note: height+2 in "to" to work around an array bounds overrun
148 in gluBuild2DMipmaps / gluScaleImage.
150 XImage *from = image;
151 XImage *to = XCreateImage (dpy, visual, 32, /* depth */
152 ZPixmap, 0, 0, from->width, from->height + 2,
155 to->data = (char *) calloc (to->height, to->bytes_per_line);
157 /* Set the bit order in the XImage structure to whatever the
158 local host's native bit order is.
160 to->bitmap_bit_order =
162 (bigendian() ? MSBFirst : LSBFirst);
164 if (visual_class (screen, visual) == PseudoColor ||
165 visual_class (screen, visual) == GrayScale)
167 Colormap cmap = DefaultColormapOfScreen (screen);
168 int ncolors = visual_cells (screen, visual);
170 colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
171 for (i = 0; i < ncolors; i++)
173 XQueryColors (dpy, cmap, colors, ncolors);
176 if (colors == 0) /* truecolor */
178 srmsk = to->red_mask;
179 sgmsk = to->green_mask;
180 sbmsk = to->blue_mask;
182 decode_mask (srmsk, &srpos, &srsiz);
183 decode_mask (sgmsk, &sgpos, &sgsiz);
184 decode_mask (sbmsk, &sbpos, &sbsiz);
187 /* Pack things in "RGBA" order in client endianness. */
189 crpos = 24, cgpos = 16, cbpos = 8, capos = 0;
191 crpos = 0, cgpos = 8, cbpos = 16, capos = 24;
193 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 /* trying to track down an intermittent crash in ximage_putpixel_32 */
205 if (to->width < from->width) abort();
206 if (to->height < from->height) abort();
208 for (y = 0; y < from->height; y++)
209 for (x = 0; x < from->width; x++)
211 unsigned long sp = XGetPixel (from, x, y);
212 unsigned char sr, sg, sb;
217 sr = colors[sp].red & 0xFF;
218 sg = colors[sp].green & 0xFF;
219 sb = colors[sp].blue & 0xFF;
223 sr = (sp & srmsk) >> srpos;
224 sg = (sp & sgmsk) >> sgpos;
225 sb = (sp & sbmsk) >> sbpos;
227 sr = spread_map[0][sr];
228 sg = spread_map[1][sg];
229 sb = spread_map[2][sb];
232 cp = ((sr << crpos) |
237 XPutPixel (to, x, y, cp);
240 if (colors) free (colors);
245 #endif /* REFORMAT_IMAGE_DATA */
247 /* Shrinks the XImage by a factor of two.
248 We use this when mipmapping fails on large textures.
251 halve_image (XImage *ximage, XRectangle *geom)
253 int w2 = ximage->width/2;
254 int h2 = ximage->height/2;
258 if (w2 <= 32 || h2 <= 32) /* let's not go crazy here, man. */
262 fprintf (stderr, "%s: shrinking image %dx%d -> %dx%d\n",
263 progname, ximage->width, ximage->height, w2, h2);
265 ximage2 = (XImage *) calloc (1, sizeof (*ximage2));
268 ximage2->height = h2;
269 ximage2->bytes_per_line = 0;
271 XInitImage (ximage2);
273 ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
276 fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
277 progname, ximage->width, ximage->height, w2, h2);
281 for (y = 0; y < h2; y++)
282 for (x = 0; x < w2; x++)
283 XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
300 #ifdef REFORMAT_IMAGE_DATA
302 /* Pulls the Pixmap bits from the server and returns an XImage
303 in some format acceptable to OpenGL.
306 pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
308 Display *dpy = DisplayOfScreen (screen);
309 unsigned int width, height, depth;
311 # ifdef HAVE_XSHM_EXTENSION
312 Bool use_shm = get_boolean_resource (dpy, "useSHM", "Boolean");
313 XShmSegmentInfo shm_info;
314 # endif /* HAVE_XSHM_EXTENSION */
316 XImage *server_ximage = 0;
317 XImage *client_ximage = 0;
323 XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
326 if (width < 5 || height < 5) /* something's gone wrong somewhere... */
329 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
331 # ifdef HAVE_XSHM_EXTENSION
334 Visual *visual = DefaultVisualOfScreen (screen);
335 server_ximage = create_xshm_image (dpy, visual, depth,
336 ZPixmap, 0, &shm_info,
339 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
343 # endif /* HAVE_XSHM_EXTENSION */
346 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
348 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
350 # ifdef HAVE_XSHM_EXTENSION
352 destroy_xshm_image (dpy, server_ximage, &shm_info);
354 # endif /* HAVE_XSHM_EXTENSION */
355 XDestroyImage (server_ximage);
357 return client_ximage;
361 # else /* ! REFORMAT_IMAGE_DATA */
364 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
365 GLint type, format; /* ...use this. */
368 /* Abbreviate these so that the table entries all fit on one line...
370 #define BYTE GL_UNSIGNED_BYTE
371 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
372 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
373 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
374 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
375 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
376 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
377 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
378 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
379 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
380 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
381 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
382 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
384 static const conversion_table ctable[] = {
385 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
386 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
387 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
388 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
389 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
390 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
391 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
392 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
393 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
394 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
395 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
396 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
397 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
398 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
399 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
400 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
401 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
402 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
403 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
404 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
405 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
406 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
407 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
408 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
409 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
410 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
414 /* Given an XImage, returns the GL settings to use its data as a texture.
417 gl_settings_for_ximage (XImage *image,
418 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
421 for (i = 0; i < countof(ctable); ++i)
423 if (image->bits_per_pixel == ctable[i].depth &&
424 image->red_mask == ctable[i].red_mask &&
425 image->green_mask == ctable[i].green_mask &&
426 image->blue_mask == ctable[i].blue_mask)
428 *type_ret = ctable[i].type;
429 *format_ret = ctable[i].format;
431 if (image->bits_per_pixel == 24)
433 /* don't know how to test this... */
434 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
439 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
444 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
446 (*format_ret == GL_RGB ? "RGB" :
447 *format_ret == GL_BGR ? "BGR" :
448 *format_ret == GL_RGBA ? "RGBA" :
449 *format_ret == GL_BGRA ? "BGRA" :
450 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
452 (*type_ret == BYTE ? "BYTE" :
453 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
454 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
455 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
456 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
457 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
458 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
459 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
460 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
461 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
462 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
463 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
464 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
467 image->bits_per_pixel,
468 image->red_mask, image->green_mask, image->blue_mask);
475 /* Unknown RGB fields? */
479 #endif /* ! REFORMAT_IMAGE_DATA */
482 GLXContext glx_context;
484 int pix_width, pix_height, pix_depth;
489 /* Used in async mode
491 void (*callback) (const char *filename, XRectangle *geometry,
492 int iw, int ih, int tw, int th,
498 char **filename_return;
499 XRectangle *geometry_return;
500 int *image_width_return;
501 int *image_height_return;
502 int *texture_width_return;
503 int *texture_height_return;
508 /* Returns the current time in seconds as a double.
514 # ifdef GETTIMEOFDAY_TWO_ARGS
516 gettimeofday(&now, &tzp);
521 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
525 /* return the next larger power of 2. */
529 static const unsigned int pow2[] = {
530 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
531 2048, 4096, 8192, 16384, 32768, 65536 };
533 for (j = 0; j < countof(pow2); j++)
534 if (pow2[j] >= i) return pow2[j];
535 abort(); /* too big! */
539 /* Loads the given XImage into GL's texture memory.
540 The image may be of any size.
541 If mipmap_p is true, then make mipmaps instead of just a single texture.
542 Writes to stderr and returns False on error.
545 ximage_to_texture (XImage *ximage,
546 GLint type, GLint format,
549 XRectangle *geometry,
552 int max_reduction = 7;
555 int orig_width = ximage->width;
556 int orig_height = ximage->height;
564 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
565 tex_width = ximage->width;
566 tex_height = ximage->height;
569 fprintf (stderr, "%s: mipmap %d x %d\n",
570 progname, ximage->width, ximage->height);
572 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
573 format, type, ximage->data);
578 /* glTexImage2D() requires the texture sizes to be powers of 2.
579 So first, create a texture of that size (but don't write any
582 tex_width = to_pow2 (ximage->width);
583 tex_height = to_pow2 (ximage->height);
586 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
587 progname, ximage->width, ximage->height,
588 tex_width, tex_height);
590 glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
594 /* Now load our non-power-of-2 image data into the existing texture. */
597 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
598 ximage->width, ximage->height,
599 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
607 const char *s = (char *) gluErrorString (err);
611 sprintf (buf, "unknown error %d", (int) err);
615 while (glGetError() != GL_NO_ERROR)
616 ; /* clear any lingering errors */
618 if (++err_count > max_reduction)
622 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
623 "%s: The error was: \"%s\".\n"
624 "%s: probably this means "
625 "\"your video card is worthless and weak\"?\n\n",
626 progname, orig_width, orig_height,
627 ximage->width, ximage->height,
635 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
636 progname, ximage->width, ximage->height, s);
637 halve_image (ximage, geometry);
642 if (width_return) *width_return = tex_width;
643 if (height_return) *height_return = tex_height;
648 static void load_texture_async_cb (Screen *screen,
649 Window window, Drawable drawable,
650 const char *name, XRectangle *geometry,
654 /* Grabs an image of the desktop (or another random image file) and
655 loads the image into GL's texture memory.
656 When the callback is called, the image data will have been loaded
657 into texture number `texid' (via glBindTexture.)
659 If an error occurred, width/height will be 0.
662 load_texture_async (Screen *screen, Window window,
663 GLXContext glx_context,
664 int desired_width, int desired_height,
667 void (*callback) (const char *filename,
668 XRectangle *geometry,
676 Display *dpy = DisplayOfScreen (screen);
677 XWindowAttributes xgwa;
678 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
681 data->load_time = double_time();
684 data->mipmap_p = mipmap_p;
685 data->glx_context = glx_context;
686 data->callback = callback;
687 data->closure = closure;
689 XGetWindowAttributes (dpy, window, &xgwa);
690 data->pix_width = xgwa.width;
691 data->pix_height = xgwa.height;
692 data->pix_depth = xgwa.depth;
694 if (desired_width && desired_width < xgwa.width)
695 data->pix_width = desired_width;
696 if (desired_height && desired_height < xgwa.height)
697 data->pix_height = desired_height;
699 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
701 load_image_async (screen, window, data->pixmap,
702 load_texture_async_cb, data);
706 /* Once we have an XImage, this loads it into GL.
707 This is used in both synchronous and asynchronous mode.
710 load_texture_async_cb (Screen *screen, Window window, Drawable drawable,
711 const char *name, XRectangle *geometry, void *closure)
713 Display *dpy = DisplayOfScreen (screen);
717 int iw=0, ih=0, tw=0, th=0;
718 double cvt_time=0, tex_time=0, done_time=0;
719 img_closure *data = (img_closure *) closure;
720 /* copy closure data to stack and free the original before running cb */
721 img_closure dd = *data;
722 memset (data, 0, sizeof (*data));
727 glXMakeCurrent (dpy, window, dd.glx_context);
729 if (geometry->width <= 0 || geometry->height <= 0)
731 /* This can happen if an old version of xscreensaver-getimage
735 geometry->width = dd.pix_width;
736 geometry->height = dd.pix_height;
739 if (geometry->width <= 0 || geometry->height <= 0)
743 cvt_time = double_time();
745 # ifdef REFORMAT_IMAGE_DATA
746 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
748 type = GL_UNSIGNED_BYTE;
750 #else /* ! REFORMAT_IMAGE_DATA */
752 Visual *visual = DefaultVisualOfScreen (screen);
755 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
756 dd.pix_width, dd.pix_height, 32, 0);
758 /* Note: height+2 in "to" to be to work around an array bounds overrun
759 in gluBuild2DMipmaps / gluScaleImage. */
760 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
763 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
764 ~0L, ximage->format, ximage, 0, 0))
766 XDestroyImage (ximage);
770 gl_settings_for_ximage (ximage, &type, &format, &swap);
771 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
773 #endif /* REFORMAT_IMAGE_DATA */
775 XFreePixmap (dpy, dd.pixmap);
779 tex_time = double_time();
788 glBindTexture (GL_TEXTURE_2D, dd.texid);
790 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
791 ok = ximage_to_texture (ximage, type, format, &tw, &th, geometry,
795 iw = ximage->width; /* in case the image was shrunk */
800 if (ximage) XDestroyImage (ximage);
803 iw = ih = tw = th = 0;
806 done_time = double_time();
810 /* prints: A + B + C = D
811 A = file I/O time (happens in background)
812 B = time to pull bits from server (this process)
813 C = time to convert bits to GL textures (this process)
814 D = total elapsed time from "want image" to "see image"
816 B+C is responsible for any frame-rate glitches.
818 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
820 cvt_time - dd.load_time,
822 done_time - tex_time,
823 done_time - dd.load_time);
826 /* asynchronous mode */
827 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
830 /* synchronous mode */
831 if (dd.filename_return) *dd.filename_return = (char *) name;
832 if (dd.geometry_return) *dd.geometry_return = *geometry;
833 if (dd.image_width_return) *dd.image_width_return = iw;
834 if (dd.image_height_return) *dd.image_height_return = ih;
835 if (dd.texture_width_return) *dd.texture_width_return = tw;
836 if (dd.texture_height_return) *dd.texture_height_return = th;