1 /* grab-ximage.c --- grab the screen to an XImage for use with OpenGL.
2 * xscreensaver, Copyright (c) 2001-2006 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 /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
324 # ifdef HAVE_XSHM_EXTENSION
327 Visual *visual = DefaultVisualOfScreen (screen);
328 server_ximage = create_xshm_image (dpy, visual, depth,
329 ZPixmap, 0, &shm_info,
332 XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
336 # endif /* HAVE_XSHM_EXTENSION */
339 server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
341 client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
343 # ifdef HAVE_XSHM_EXTENSION
345 destroy_xshm_image (dpy, server_ximage, &shm_info);
347 # endif /* HAVE_XSHM_EXTENSION */
348 XDestroyImage (server_ximage);
350 return client_ximage;
354 # else /* ! REFORMAT_IMAGE_DATA */
357 unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
358 GLint type, format; /* ...use this. */
361 /* Abbreviate these so that the table entries all fit on one line...
363 #define BYTE GL_UNSIGNED_BYTE
364 #define BYTE_2_3_3_REV GL_UNSIGNED_BYTE_2_3_3_REV
365 #define BYTE_3_3_2 GL_UNSIGNED_BYTE_3_3_2
366 #define INT_10_10_10_2 GL_UNSIGNED_INT_10_10_10_2
367 #define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
368 #define INT_8_8_8_8 GL_UNSIGNED_INT_8_8_8_8
369 #define INT_8_8_8_8_REV GL_UNSIGNED_INT_8_8_8_8_REV
370 #define SHORT_1_5_5_5_REV GL_UNSIGNED_SHORT_1_5_5_5_REV
371 #define SHORT_4_4_4_4 GL_UNSIGNED_SHORT_4_4_4_4
372 #define SHORT_4_4_4_4_REV GL_UNSIGNED_SHORT_4_4_4_4_REV
373 #define SHORT_5_5_5_1 GL_UNSIGNED_SHORT_5_5_5_1
374 #define SHORT_5_6_5 GL_UNSIGNED_SHORT_5_6_5
375 #define SHORT_5_6_5_REV GL_UNSIGNED_SHORT_5_6_5_REV
377 static const conversion_table ctable[] = {
378 { 8, 0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2, GL_RGB },
379 { 8, 0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV, GL_RGB },
380 { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5, GL_RGB },
381 { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV, GL_RGB },
382 { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4, GL_RGBA },
383 { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4, GL_BGRA },
384 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4, GL_ABGR_EXT },
385 { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV, GL_RGBA },
386 { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV, GL_BGRA },
387 { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1, GL_RGBA },
388 { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1, GL_BGRA },
389 { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1, GL_ABGR_EXT },
390 { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV, GL_RGBA },
391 { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV, GL_BGRA },
392 { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8, GL_RGBA },
393 { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8, GL_BGRA },
394 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8, GL_ABGR_EXT },
395 { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV, GL_RGBA },
396 { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV, GL_BGRA },
397 { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2, GL_RGBA },
398 { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2, GL_BGRA },
399 { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2, GL_ABGR_EXT },
400 { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA },
401 { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA },
402 { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE, GL_RGB },
403 { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE, GL_BGR },
407 /* Given an XImage, returns the GL settings to use its data as a texture.
410 gl_settings_for_ximage (XImage *image,
411 GLint *type_ret, GLint *format_ret, GLint *swap_ret)
414 for (i = 0; i < countof(ctable); ++i)
416 if (image->bits_per_pixel == ctable[i].depth &&
417 image->red_mask == ctable[i].red_mask &&
418 image->green_mask == ctable[i].green_mask &&
419 image->blue_mask == ctable[i].blue_mask)
421 *type_ret = ctable[i].type;
422 *format_ret = ctable[i].format;
424 if (image->bits_per_pixel == 24)
426 /* don't know how to test this... */
427 *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
432 *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
437 fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
439 (*format_ret == GL_RGB ? "RGB" :
440 *format_ret == GL_BGR ? "BGR" :
441 *format_ret == GL_RGBA ? "RGBA" :
442 *format_ret == GL_BGRA ? "BGRA" :
443 *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
445 (*type_ret == BYTE ? "BYTE" :
446 *type_ret == BYTE_3_3_2 ? "BYTE_3_3_2" :
447 *type_ret == BYTE_2_3_3_REV ? "BYTE_2_3_3_REV" :
448 *type_ret == INT_8_8_8_8 ? "INT_8_8_8_8" :
449 *type_ret == INT_8_8_8_8_REV ? "INT_8_8_8_8_REV" :
450 *type_ret == INT_10_10_10_2 ? "INT_10_10_10_2" :
451 *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
452 *type_ret == SHORT_4_4_4_4 ? "SHORT_4_4_4_4" :
453 *type_ret == SHORT_4_4_4_4_REV ? "SHORT_4_4_4_4_REV" :
454 *type_ret == SHORT_5_5_5_1 ? "SHORT_5_5_5_1" :
455 *type_ret == SHORT_1_5_5_5_REV ? "SHORT_1_5_5_5_REV" :
456 *type_ret == SHORT_5_6_5 ? "SHORT_5_6_5" :
457 *type_ret == SHORT_5_6_5_REV ? "SHORT_5_6_5_REV" :
460 image->bits_per_pixel,
461 image->red_mask, image->green_mask, image->blue_mask);
468 /* Unknown RGB fields? */
472 #endif /* ! REFORMAT_IMAGE_DATA */
475 GLXContext glx_context;
477 int pix_width, pix_height, pix_depth;
482 /* Used in async mode
484 void (*callback) (const char *filename, XRectangle *geometry,
485 int iw, int ih, int tw, int th,
491 char **filename_return;
492 XRectangle *geometry_return;
493 int *image_width_return;
494 int *image_height_return;
495 int *texture_width_return;
496 int *texture_height_return;
501 /* Returns the current time in seconds as a double.
507 # ifdef GETTIMEOFDAY_TWO_ARGS
509 gettimeofday(&now, &tzp);
514 return (now.tv_sec + ((double) now.tv_usec * 0.000001));
518 /* return the next larger power of 2. */
522 static const unsigned int pow2[] = {
523 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
524 2048, 4096, 8192, 16384, 32768, 65536 };
526 for (j = 0; j < countof(pow2); j++)
527 if (pow2[j] >= i) return pow2[j];
528 abort(); /* too big! */
532 /* Loads the given XImage into GL's texture memory.
533 The image may be of any size.
534 If mipmap_p is true, then make mipmaps instead of just a single texture.
535 Writes to stderr and returns False on error.
538 ximage_to_texture (XImage *ximage,
539 GLint type, GLint format,
542 XRectangle *geometry,
545 int max_reduction = 7;
548 int orig_width = ximage->width;
549 int orig_height = ximage->height;
557 /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
558 tex_width = ximage->width;
559 tex_height = ximage->height;
562 fprintf (stderr, "%s: mipmap %d x %d\n",
563 progname, ximage->width, ximage->height);
565 gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
566 format, type, ximage->data);
571 /* glTexImage2D() requires the texture sizes to be powers of 2.
572 So first, create a texture of that size (but don't write any
575 tex_width = to_pow2 (ximage->width);
576 tex_height = to_pow2 (ximage->height);
579 fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
580 progname, ximage->width, ximage->height,
581 tex_width, tex_height);
583 glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
587 /* Now load our non-power-of-2 image data into the existing texture. */
590 glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
591 ximage->width, ximage->height,
592 GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
600 const char *s = (char *) gluErrorString (err);
604 sprintf (buf, "unknown error %d", (int) err);
608 while (glGetError() != GL_NO_ERROR)
609 ; /* clear any lingering errors */
611 if (++err_count > max_reduction)
615 "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
616 "%s: The error was: \"%s\".\n"
617 "%s: probably this means "
618 "\"your video card is worthless and weak\"?\n\n",
619 progname, orig_width, orig_height,
620 ximage->width, ximage->height,
628 fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
629 progname, ximage->width, ximage->height, s);
630 halve_image (ximage, geometry);
635 if (width_return) *width_return = tex_width;
636 if (height_return) *height_return = tex_height;
641 static void load_texture_async_cb (Screen *screen,
642 Window window, Drawable drawable,
643 const char *name, XRectangle *geometry,
647 /* Grabs an image of the desktop (or another random image file) and
648 loads the image into GL's texture memory.
649 When the callback is called, the image data will have been loaded
650 into texture number `texid' (via glBindTexture.)
652 If an error occurred, width/height will be 0.
655 load_texture_async (Screen *screen, Window window,
656 GLXContext glx_context,
657 int desired_width, int desired_height,
660 void (*callback) (const char *filename,
661 XRectangle *geometry,
669 Display *dpy = DisplayOfScreen (screen);
670 XWindowAttributes xgwa;
671 img_closure *data = (img_closure *) calloc (1, sizeof(*data));
674 data->load_time = double_time();
677 data->mipmap_p = mipmap_p;
678 data->glx_context = glx_context;
679 data->callback = callback;
680 data->closure = closure;
682 XGetWindowAttributes (dpy, window, &xgwa);
683 data->pix_width = xgwa.width;
684 data->pix_height = xgwa.height;
685 data->pix_depth = xgwa.depth;
687 if (desired_width && desired_width < xgwa.width)
688 data->pix_width = desired_width;
689 if (desired_height && desired_height < xgwa.height)
690 data->pix_height = desired_height;
692 data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
694 load_image_async (screen, window, data->pixmap,
695 load_texture_async_cb, data);
699 /* Once we have an XImage, this loads it into GL.
700 This is used in both synchronous and asynchronous mode.
703 load_texture_async_cb (Screen *screen, Window window, Drawable drawable,
704 const char *name, XRectangle *geometry, void *closure)
706 Display *dpy = DisplayOfScreen (screen);
710 int iw=0, ih=0, tw=0, th=0;
711 double cvt_time=0, tex_time=0, done_time=0;
712 img_closure *data = (img_closure *) closure;
713 /* copy closure data to stack and free the original before running cb */
714 img_closure dd = *data;
715 memset (data, 0, sizeof (*data));
720 glXMakeCurrent (dpy, window, dd.glx_context);
722 if (geometry->width <= 0 || geometry->height <= 0)
724 /* This can happen if an old version of xscreensaver-getimage
728 geometry->width = dd.pix_width;
729 geometry->height = dd.pix_height;
732 if (geometry->width <= 0 || geometry->height <= 0)
736 cvt_time = double_time();
738 # ifdef REFORMAT_IMAGE_DATA
739 ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
741 type = GL_UNSIGNED_BYTE;
743 #else /* ! REFORMAT_IMAGE_DATA */
745 Visual *visual = DefaultVisualOfScreen (screen);
748 ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
749 dd.pix_width, dd.pix_height, 32, 0);
751 /* Note: height+2 in "to" to be to work around an array bounds overrun
752 in gluBuild2DMipmaps / gluScaleImage. */
753 ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
756 !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
757 ~0L, ximage->format, ximage, 0, 0))
759 XDestroyImage (ximage);
763 gl_settings_for_ximage (ximage, &type, &format, &swap);
764 glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
766 #endif /* REFORMAT_IMAGE_DATA */
768 XFreePixmap (dpy, dd.pixmap);
772 tex_time = double_time();
781 glBindTexture (GL_TEXTURE_2D, dd.texid);
783 glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
784 ok = ximage_to_texture (ximage, type, format, &tw, &th, geometry,
788 iw = ximage->width; /* in case the image was shrunk */
793 if (ximage) XDestroyImage (ximage);
796 iw = ih = tw = th = 0;
799 done_time = double_time();
803 /* prints: A + B + C = D
804 A = file I/O time (happens in background)
805 B = time to pull bits from server (this process)
806 C = time to convert bits to GL textures (this process)
807 D = total elapsed time from "want image" to "see image"
809 B+C is responsible for any frame-rate glitches.
811 "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
813 cvt_time - dd.load_time,
815 done_time - tex_time,
816 done_time - dd.load_time);
819 /* asynchronous mode */
820 dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
823 /* synchronous mode */
824 if (dd.filename_return) *dd.filename_return = (char *) name;
825 if (dd.geometry_return) *dd.geometry_return = *geometry;
826 if (dd.image_width_return) *dd.image_width_return = iw;
827 if (dd.image_height_return) *dd.image_height_return = ih;
828 if (dd.texture_width_return) *dd.texture_width_return = tw;
829 if (dd.texture_height_return) *dd.texture_height_return = th;