http://ftp.ksu.edu.tw/FTP/FreeBSD/distfiles/xscreensaver-4.20.tar.gz
[xscreensaver] / hacks / glx / grab-ximage.c
index 6000e563c7b6bb5fa14a1f030e64bac22b2e0b2c..519f8c453e8f70d825679f80369df40ce77a5674 100644 (file)
@@ -1,5 +1,5 @@
 /* grab-ximage.c --- grab the screen to an XImage for use with OpenGL.
- * xscreensaver, Copyright (c) 2001, 2003 Jamie Zawinski <jwz@jwz.org>
+ * xscreensaver, Copyright (c) 2001, 2003, 2005 Jamie Zawinski <jwz@jwz.org>
  *
  * Permission to use, copy, modify, distribute, and sell this software and its
  * documentation for any purpose is hereby granted without fee, provided that
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
 #include <GL/gl.h>     /* only for GLfloat */
+#include <GL/glu.h>    /* for gluBuild2DMipmaps */
 
 #include "grabscreen.h"
 #include "visual.h"
 
+/* If REFORMAT_IMAGE_DATA is defined, then we convert Pixmaps to textures
+   like this:
+
+     - get Pixmap as an XImage in whatever form the server hands us;
+     - convert that XImage to 32-bit RGBA in client-local endianness;
+     - make the texture using RGBA, UNSIGNED_BYTE.
+
+   If undefined, we do this:
+
+     - get Pixmap as an XImage in whatever form the server hands us;
+     - figure out what OpenGL texture packing parameters correspond to
+       the image data that the server sent us and use that, e.g.,
+       BGRA, INT_8_8_8_8_REV.
+
+   You might expect the second method to be faster, since we're not making
+   a second copy of the data and iterating each pixel before we hand it
+   to GL.  But, you'd be wrong.  The first method is almost 6x faster.
+   I guess GL is reformatting it *again*, and doing it very inefficiently!
+*/
+#define REFORMAT_IMAGE_DATA
+
+
+#ifdef HAVE_XSHM_EXTENSION
+# include "resources.h"
+# include "xshm.h"
+#endif /* HAVE_XSHM_EXTENSION */
+
 extern char *progname;
 
 #include <X11/Xutil.h>
@@ -35,19 +63,20 @@ extern char *progname;
 #undef countof
 #define countof(x) (sizeof((x))/sizeof((*x)))
 
-/* return the next larger power of 2. */
-static int
-to_pow2 (int i)
+
+static int debug_p = 0;
+
+static Bool
+bigendian (void)
 {
-  static unsigned int pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
-                                 2048, 4096, 8192, 16384, 32768, 65536 };
-  int j;
-  for (j = 0; j < countof(pow2); j++)
-    if (pow2[j] >= i) return pow2[j];
-  abort();  /* too big! */
+  union { int i; char c[sizeof(int)]; } u;
+  u.i = 1;
+  return !u.c[0];
 }
 
 
+#ifdef REFORMAT_IMAGE_DATA
+
 /* Given a bitmask, returns the position and width of the field.
  */
 static void
@@ -87,185 +116,355 @@ spread_bits (unsigned char value, unsigned char width)
 }
 
 
-static Bool
-bigendian (void)
-{
-  union { int i; char c[sizeof(int)]; } u;
-  u.i = 1;
-  return !u.c[0];
-}
-
-
 static XImage *
-screen_to_ximage_1 (Screen *screen, Window window, Pixmap pixmap)
+convert_ximage_to_rgba32 (Screen *screen, XImage *image)
 {
   Display *dpy = DisplayOfScreen (screen);
-  XWindowAttributes xgwa;
-  int win_width, win_height;
-  int tex_width, tex_height;
-
-  XGetWindowAttributes (dpy, window, &xgwa);
-  win_width = xgwa.width;
-  win_height = xgwa.height;
+  Visual *visual = DefaultVisualOfScreen (screen);
 
-  /* GL texture sizes must be powers of two. */
-  tex_width  = to_pow2(win_width);
-  tex_height = to_pow2(win_height);
+  int x, y;
+  unsigned int crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
+  unsigned int srpos=0, sgpos=0, sbpos=0;
+  unsigned int srmsk=0, sgmsk=0, sbmsk=0;
+  unsigned int srsiz=0, sgsiz=0, sbsiz=0;
+  int i;
+  XColor *colors = 0;
+  unsigned char spread_map[3][256];
 
-  /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
+  /* Note: height+2 in "to" to be to work around an array bounds overrun
+     in gluBuild2DMipmaps / gluScaleImage.
    */
-  {
-    XImage *ximage1, *ximage2;
-    XColor *colors = 0;
+  XImage *from = image;
+  XImage *to = XCreateImage (dpy, visual, 32,  /* depth */
+                             ZPixmap, 0, 0, from->width, from->height + 2,
+                             32, /* bitmap pad */
+                             0);
+  to->data = (char *) calloc (to->height, to->bytes_per_line);
+
+  if (visual_class (screen, visual) == PseudoColor ||
+      visual_class (screen, visual) == GrayScale)
+    {
+      Colormap cmap = DefaultColormapOfScreen (screen);
+      int ncolors = visual_cells (screen, visual);
+      int i;
+      colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
+      for (i = 0; i < ncolors; i++)
+        colors[i].pixel = i;
+      XQueryColors (dpy, cmap, colors, ncolors);
+    }
 
-    ximage1 = XGetImage (dpy, pixmap, 0, 0, win_width, win_height, ~0L,
-                         ZPixmap);
-    XFreePixmap (dpy, pixmap);
-    pixmap = 0;
+  if (colors == 0)  /* truecolor */
+    {
+      srmsk = to->red_mask;
+      sgmsk = to->green_mask;
+      sbmsk = to->blue_mask;
 
-    ximage2 = XCreateImage (dpy, xgwa.visual, 32, ZPixmap, 0, 0,
-                            tex_width, tex_height, 32, 0);
+      decode_mask (srmsk, &srpos, &srsiz);
+      decode_mask (sgmsk, &sgpos, &sgsiz);
+      decode_mask (sbmsk, &sbpos, &sbsiz);
+    }
 
-    ximage2->data = (char *) calloc (tex_height, ximage2->bytes_per_line);
+  /* Pack things in "RGBA" order in client endianness. */
+  if (bigendian())
+    crpos = 24, cgpos = 16, cbpos =  8, capos =  0;
+  else
+    crpos =  0, cgpos =  8, cbpos = 16, capos = 24;
 
+  if (colors == 0)  /* truecolor */
     {
-      Screen *dscreen = DefaultScreenOfDisplay (dpy);
-      Visual *dvisual = DefaultVisualOfScreen (dscreen);
-      if (visual_class (dscreen, dvisual) == PseudoColor ||
-          visual_class (dscreen, dvisual) == GrayScale)
+      for (i = 0; i < 256; i++)
         {
-          Colormap cmap = DefaultColormapOfScreen(dscreen);
-          int ncolors = visual_cells (dscreen, dvisual);
-          int i;
-          colors = (XColor *) calloc (sizeof (*colors), ncolors+1);
-          for (i = 0; i < ncolors; i++)
-            colors[i].pixel = i;
-          XQueryColors (dpy, cmap, colors, ncolors);
+          spread_map[0][i] = spread_bits (i, srsiz);
+          spread_map[1][i] = spread_bits (i, sgsiz);
+          spread_map[2][i] = spread_bits (i, sbsiz);
         }
     }
 
-    /* Translate the server-ordered image to a client-ordered image.
-     */
+  for (y = 0; y < from->height; y++)
+    for (x = 0; x < from->width; x++)
+      {
+        unsigned long sp = XGetPixel (from, x, y);
+        unsigned char sr, sg, sb;
+        unsigned long cp;
+
+        if (colors)
+          {
+            sr = colors[sp].red   & 0xFF;
+            sg = colors[sp].green & 0xFF;
+            sb = colors[sp].blue  & 0xFF;
+          }
+        else
+          {
+            sr = (sp & srmsk) >> srpos;
+            sg = (sp & sgmsk) >> sgpos;
+            sb = (sp & sbmsk) >> sbpos;
+
+            sr = spread_map[0][sr];
+            sg = spread_map[1][sg];
+            sb = spread_map[2][sb];
+          }
+
+        cp = ((sr << crpos) |
+              (sg << cgpos) |
+              (sb << cbpos) |
+              (0xFF << capos));
+
+        XPutPixel (to, x, y, cp);
+      }
+
+  if (colors) free (colors);
+
+  return to;
+}
+
+#endif /* REFORMAT_IMAGE_DATA */
+
+/* Shrinks the XImage by a factor of two.
+   We use this when mipmapping fails on large textures.
+ */
+static void
+halve_image (XImage *ximage)
+{
+  int w2 = ximage->width/2;
+  int h2 = ximage->height/2;
+  int x, y;
+  XImage *ximage2;
+
+  if (w2 <= 32 || h2 <= 32)   /* let's not go crazy here, man. */
+    return;
+
+  if (debug_p)
+    fprintf (stderr, "%s: shrinking image %dx%d -> %dx%d\n",
+             progname, ximage->width, ximage->height, w2, h2);
+
+  ximage2 = (XImage *) calloc (1, sizeof (*ximage2));
+  *ximage2 = *ximage;
+  ximage2->width = w2;
+  ximage2->height = h2;
+  ximage2->bytes_per_line = 0;
+  ximage2->data = 0;
+  XInitImage (ximage2);
+
+  ximage2->data = (char *) calloc (h2, ximage2->bytes_per_line);
+  if (!ximage2->data)
     {
-      int x, y;
-      unsigned int crpos=0, cgpos=0, cbpos=0, capos=0; /* bitfield positions */
-      unsigned int srpos=0, sgpos=0, sbpos=0;
-      unsigned int srmsk=0, sgmsk=0, sbmsk=0;
-      unsigned int srsiz=0, sgsiz=0, sbsiz=0;
-      int i;
+      fprintf (stderr, "%s: out of memory (scaling %dx%d image to %dx%d)\n",
+               progname, ximage->width, ximage->height, w2, h2);
+      exit (1);
+    }
 
-      unsigned char spread_map[3][256];
+  for (y = 0; y < h2; y++)
+    for (x = 0; x < w2; x++)
+      XPutPixel (ximage2, x, y, XGetPixel (ximage, x*2, y*2));
 
-      if (colors == 0)  /* truecolor */
-        {
-          srmsk = ximage2->red_mask;
-          sgmsk = ximage2->green_mask;
-          sbmsk = ximage2->blue_mask;
+  free (ximage->data);
+  *ximage = *ximage2;
+  ximage2->data = 0;
+  XFree (ximage2);
+}
 
-          decode_mask (srmsk, &srpos, &srsiz);
-          decode_mask (sgmsk, &sgpos, &sgsiz);
-          decode_mask (sbmsk, &sbpos, &sbsiz);
-        }
 
-      /* Note that unlike X, which is endianness-agnostic (since any XImage
-         can have its own specific bit ordering, with the server reversing
-         things as necessary) OpenGL pretends everything is client-side, so
-         we need to pack things in "RGBA" order on the client machine,
-         regardless of its endianness.
-       */
-      if (bigendian())
-        crpos = 24, cgpos = 16, cbpos =  8, capos =  0;
-      else
-        crpos =  0, cgpos =  8, cbpos = 16, capos = 24;
+#ifdef REFORMAT_IMAGE_DATA
 
-      if (colors == 0)  /* truecolor */
-        {
-          for (i = 0; i < 256; i++)
-            {
-              spread_map[0][i] = spread_bits (i, srsiz);
-              spread_map[1][i] = spread_bits (i, sgsiz);
-              spread_map[2][i] = spread_bits (i, sbsiz);
-            }
-        }
+/* Pulls the Pixmap bits from the server and returns an XImage
+   in some format acceptable to OpenGL.
+ */
+static XImage *
+pixmap_to_gl_ximage (Screen *screen, Window window, Pixmap pixmap)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Visual *visual = DefaultVisualOfScreen (screen);
+  unsigned int width, height, depth;
 
-      for (y = 0; y < win_height; y++)
-        {
-          int y2 = (win_height-1-y); /* Texture maps are upside down. */
-          for (x = 0; x < win_width; x++)
-            {
-              unsigned long sp = XGetPixel (ximage1, x, y2);
-              unsigned char sr, sg, sb;
-              unsigned long cp;
-
-              if (colors)
-                {
-                  sr = colors[sp].red   & 0xFF;
-                  sg = colors[sp].green & 0xFF;
-                  sb = colors[sp].blue  & 0xFF;
-                }
-              else
-                {
-                  sr = (sp & srmsk) >> srpos;
-                  sg = (sp & sgmsk) >> sgpos;
-                  sb = (sp & sbmsk) >> sbpos;
-
-                  sr = spread_map[0][sr];
-                  sg = spread_map[1][sg];
-                  sb = spread_map[2][sb];
-                }
-
-              cp = ((sr << crpos) |
-                    (sg << cgpos) |
-                    (sb << cbpos) |
-                    (0xFF << capos));
-
-              XPutPixel (ximage2, x, y, cp);
-            }
-        }
+# ifdef HAVE_XSHM_EXTENSION
+  Bool use_shm = get_boolean_resource ("useSHM", "Boolean");
+  XShmSegmentInfo shm_info;
+# endif /* HAVE_XSHM_EXTENSION */
+
+  XImage *server_ximage = 0;
+  XImage *client_ximage = 0;
+
+  {
+    Window root;
+    int x, y;
+    unsigned int bw;
+    XGetGeometry (dpy, pixmap, &root, &x, &y, &width, &height, &bw, &depth);
+  }
+
+  /* Convert the server-side Pixmap to a client-side GL-ordered XImage.
+   */
+# ifdef HAVE_XSHM_EXTENSION
+  if (use_shm)
+    {
+      server_ximage = create_xshm_image (dpy, visual, depth,
+                                         ZPixmap, 0, &shm_info,
+                                         width, height);
+      if (server_ximage)
+        XShmGetImage (dpy, pixmap, server_ximage, 0, 0, ~0L);
+      else
+        use_shm = False;
     }
+# endif /* HAVE_XSHM_EXTENSION */
 
-    if (pixmap) XFreePixmap (dpy, pixmap);
-    if (colors) free (colors);
-    free (ximage1->data);
-    ximage1->data = 0;
-    XDestroyImage (ximage1);
+  if (!server_ximage)
+    server_ximage = XGetImage (dpy, pixmap, 0, 0, width, height, ~0L, ZPixmap);
 
-#if 0
-    fprintf(stderr, "%s: grabbed %dx%d window 0x%lx to %dx%d texture\n",
-            progname, win_width, win_height, (unsigned long) window,
-            tex_width, tex_height);
-#endif
+  client_ximage = convert_ximage_to_rgba32 (screen, server_ximage);
 
-    return ximage2;
-  }
+# ifdef HAVE_XSHM_EXTENSION
+  if (use_shm)
+    destroy_xshm_image (dpy, server_ximage, &shm_info);
+  else
+# endif /* HAVE_XSHM_EXTENSION */
+    XDestroyImage (server_ximage);
+
+  return client_ximage;
 }
 
 
-/* Returns an XImage structure containing an image of the desktop.
-   (As a side-effect, that image *may* be painted onto the given Window.)
-   This XImage will be 32 bits per pixel, 8 each per R, G, and B, with the
-   extra byte set to 0xFF.
+# else /* ! REFORMAT_IMAGE_DATA */
+
+typedef struct {
+  unsigned int depth, red_mask, green_mask, blue_mask; /* when this... */
+  GLint type, format;                                  /* ...use this. */
+} conversion_table;
+
+/* Abbreviate these so that the table entries all fit on one line...
+ */
+#define BYTE               GL_UNSIGNED_BYTE
+#define BYTE_2_3_3_REV     GL_UNSIGNED_BYTE_2_3_3_REV
+#define BYTE_3_3_2         GL_UNSIGNED_BYTE_3_3_2
+#define INT_10_10_10_2     GL_UNSIGNED_INT_10_10_10_2
+#define INT_2_10_10_10_REV GL_UNSIGNED_INT_2_10_10_10_REV
+#define INT_8_8_8_8        GL_UNSIGNED_INT_8_8_8_8
+#define INT_8_8_8_8_REV    GL_UNSIGNED_INT_8_8_8_8_REV
+#define SHORT_1_5_5_5_REV  GL_UNSIGNED_SHORT_1_5_5_5_REV
+#define SHORT_4_4_4_4      GL_UNSIGNED_SHORT_4_4_4_4
+#define SHORT_4_4_4_4_REV  GL_UNSIGNED_SHORT_4_4_4_4_REV
+#define SHORT_5_5_5_1      GL_UNSIGNED_SHORT_5_5_5_1
+#define SHORT_5_6_5        GL_UNSIGNED_SHORT_5_6_5
+#define SHORT_5_6_5_REV    GL_UNSIGNED_SHORT_5_6_5_REV
+
+static conversion_table ctable[] = {
+  { 8,  0x000000E0, 0x0000001C, 0x00000003, BYTE_3_3_2,         GL_RGB      },
+  { 8,  0x00000007, 0x00000038, 0x000000C0, BYTE_2_3_3_REV,     GL_RGB      },
+  { 16, 0x0000F800, 0x000007E0, 0x0000001F, SHORT_5_6_5,        GL_RGB      },
+  { 16, 0x0000001F, 0x000007E0, 0x0000F800, SHORT_5_6_5_REV,    GL_RGB      },
+  { 16, 0x0000F000, 0x00000F00, 0x000000F0, SHORT_4_4_4_4,      GL_RGBA     },
+  { 16, 0x000000F0, 0x00000F00, 0x0000F000, SHORT_4_4_4_4,      GL_BGRA     },
+  { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4,      GL_ABGR_EXT },
+  { 16, 0x0000000F, 0x000000F0, 0x00000F00, SHORT_4_4_4_4_REV,  GL_RGBA     },
+  { 16, 0x00000F00, 0x000000F0, 0x0000000F, SHORT_4_4_4_4_REV,  GL_BGRA     },
+  { 16, 0x0000F800, 0x000007C0, 0x0000003E, SHORT_5_5_5_1,      GL_RGBA     },
+  { 16, 0x0000003E, 0x000007C0, 0x0000F800, SHORT_5_5_5_1,      GL_BGRA     },
+  { 16, 0x00000001, 0x0000003E, 0x000007C0, SHORT_5_5_5_1,      GL_ABGR_EXT },
+  { 16, 0x0000001F, 0x000003E0, 0x00007C00, SHORT_1_5_5_5_REV,  GL_RGBA     },
+  { 16, 0x00007C00, 0x000003E0, 0x0000001F, SHORT_1_5_5_5_REV,  GL_BGRA     },
+  { 32, 0xFF000000, 0x00FF0000, 0x0000FF00, INT_8_8_8_8,        GL_RGBA     },
+  { 32, 0x0000FF00, 0x00FF0000, 0xFF000000, INT_8_8_8_8,        GL_BGRA     },
+  { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8,        GL_ABGR_EXT },
+  { 32, 0x000000FF, 0x0000FF00, 0x00FF0000, INT_8_8_8_8_REV,    GL_RGBA     },
+  { 32, 0x00FF0000, 0x0000FF00, 0x000000FF, INT_8_8_8_8_REV,    GL_BGRA     },
+  { 32, 0xFFC00000, 0x003FF000, 0x00000FFC, INT_10_10_10_2,     GL_RGBA     },
+  { 32, 0x00000FFC, 0x003FF000, 0xFFC00000, INT_10_10_10_2,     GL_BGRA     },
+  { 32, 0x00000003, 0x00000FFC, 0x003FF000, INT_10_10_10_2,     GL_ABGR_EXT },
+  { 32, 0x000003FF, 0x000FFC00, 0x3FF00000, INT_2_10_10_10_REV, GL_RGBA     },
+  { 32, 0x3FF00000, 0x000FFC00, 0x000003FF, INT_2_10_10_10_REV, GL_BGRA     },
+  { 24, 0x000000FF, 0x0000FF00, 0x00FF0000, BYTE,               GL_RGB      },
+  { 24, 0x00FF0000, 0x0000FF00, 0x000000FF, BYTE,               GL_BGR      },
+};
+
+
+/* Given an XImage, returns the GL settings to use its data as a texture.
  */
-XImage *
-screen_to_ximage (Screen *screen, Window window, char **filename_return)
+static void
+gl_settings_for_ximage (XImage *image,
+                        GLint *type_ret, GLint *format_ret, GLint *swap_ret)
 {
-  Display *dpy = DisplayOfScreen (screen);
-  Pixmap pixmap = 0;
-  XWindowAttributes xgwa;
+  int i;
+  for (i = 0; i < countof(ctable); ++i)
+    {
+      if (image->bits_per_pixel == ctable[i].depth &&
+          image->red_mask       == ctable[i].red_mask &&
+          image->green_mask     == ctable[i].green_mask &&
+          image->blue_mask      == ctable[i].blue_mask)
+        {
+          *type_ret   = ctable[i].type;
+          *format_ret = ctable[i].format;
 
-  XGetWindowAttributes (dpy, window, &xgwa);
-  pixmap = XCreatePixmap (dpy, window, xgwa.width, xgwa.height, xgwa.depth);
-  load_random_image (screen, window, pixmap, filename_return);
+          if (image->bits_per_pixel == 24)
+            {
+              /* don't know how to test this... */
+              *type_ret = (ctable[i].type == GL_RGB) ? GL_BGR : GL_RGB;
+              *swap_ret = 0;
+            }
+          else
+            {
+              *swap_ret = !!(image->byte_order == MSBFirst) ^ !!bigendian();
+            }
 
-  return screen_to_ximage_1 (screen, window, pixmap);
+          if (debug_p)
+            {
+              fprintf (stderr, "%s: using %s %s %d for %d %08lX %08lX %08lX\n",
+                       progname,
+                       (*format_ret == GL_RGB      ? "RGB" :
+                        *format_ret == GL_BGR      ? "BGR" :
+                        *format_ret == GL_RGBA     ? "RGBA" :
+                        *format_ret == GL_BGRA     ? "BGRA" :
+                        *format_ret == GL_ABGR_EXT ? "ABGR_EXT" :
+                        "???"),
+                       (*type_ret == BYTE               ? "BYTE" :
+                        *type_ret == BYTE_3_3_2         ? "BYTE_3_3_2" :
+                        *type_ret == BYTE_2_3_3_REV     ? "BYTE_2_3_3_REV" :
+                        *type_ret == INT_8_8_8_8        ? "INT_8_8_8_8" :
+                        *type_ret == INT_8_8_8_8_REV    ? "INT_8_8_8_8_REV" :
+                        *type_ret == INT_10_10_10_2     ? "INT_10_10_10_2" :
+                        *type_ret == INT_2_10_10_10_REV ? "INT_2_10_10_10_REV":
+                        *type_ret == SHORT_4_4_4_4      ? "SHORT_4_4_4_4" :
+                        *type_ret == SHORT_4_4_4_4_REV  ? "SHORT_4_4_4_4_REV" :
+                        *type_ret == SHORT_5_5_5_1      ? "SHORT_5_5_5_1" :
+                        *type_ret == SHORT_1_5_5_5_REV  ? "SHORT_1_5_5_5_REV" :
+                        *type_ret == SHORT_5_6_5        ? "SHORT_5_6_5" :
+                        *type_ret == SHORT_5_6_5_REV    ? "SHORT_5_6_5_REV" :
+                        "???"),
+                       *swap_ret,
+                       image->bits_per_pixel,
+                       image->red_mask, image->green_mask, image->blue_mask);
+            }
+
+          return;
+        }
+    }
+
+  /* Unknown RGB fields? */
+  abort();
 }
 
+#endif /* ! REFORMAT_IMAGE_DATA */
 
 typedef struct {
-  void (*callback) (Screen *, Window, XImage *,
-                    const char *name, void *closure, double cvt_time);
-  void *closure;
   Pixmap pixmap;
+  int pix_width, pix_height, pix_depth;
+  int texid;
+  Bool mipmap_p;
+  double load_time;
+
+  /* Used in async mode
+   */
+  void (*callback) (const char *filename, XRectangle *geometry,
+                    int iw, int ih, int tw, int th,
+                    void *closure);
+  void *closure;
+
+  /* Used in sync mode
+   */
+  char **filename_return;
+  XRectangle *geometry_return;
+  int *image_width_return;
+  int *image_height_return;
+  int *texture_width_return;
+  int *texture_height_return;
+
 } img_closure;
 
 
@@ -286,45 +485,362 @@ double_time (void)
 }
 
 
-static void
-img_cb (Screen *screen, Window window, Drawable drawable,
-        const char *name, void *closure)
+/* return the next larger power of 2. */
+static int
+to_pow2 (int i)
 {
-  XImage *ximage;
-  double cvt_time = double_time();
-  img_closure *data = (img_closure *) closure;
-  /* copy closure data to stack and free the original before running cb */
-  img_closure dd = *data;
-  memset (data, 0, sizeof (*data));
-  free (data);
-  data = 0;
-  ximage = screen_to_ximage_1 (screen, window, dd.pixmap);
-  dd.callback (screen, window, ximage, name, dd.closure, cvt_time);
+  static unsigned int pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024,
+                                 2048, 4096, 8192, 16384, 32768, 65536 };
+  int j;
+  for (j = 0; j < countof(pow2); j++)
+    if (pow2[j] >= i) return pow2[j];
+  abort();  /* too big! */
 }
 
 
-/* Like the above, but loads the image in the background and runs the
-   given callback once it has been loaded.
+/* Loads the given XImage into GL's texture memory.
+   The image may be of any size.
+   If mipmap_p is true, then make mipmaps instead of just a single texture.
+   Writes to stderr and returns False on error.
  */
-#include <X11/Intrinsic.h>
-extern XtAppContext app;
+static Bool
+ximage_to_texture (XImage *ximage,
+                   GLint type, GLint format,
+                   int *width_return,
+                   int *height_return,
+                   Bool mipmap_p)
+{
+  int max_reduction = 7;
+  int err_count = 0;
+  GLenum err = 0;
+  int orig_width = ximage->width;
+  int orig_height = ximage->height;
+  int tex_width = 0;
+  int tex_height = 0;
+
+ AGAIN:
+
+  if (mipmap_p)
+    {
+      /* gluBuild2DMipmaps doesn't require textures to be a power of 2. */
+      tex_width  = ximage->width;
+      tex_height = ximage->height;
 
+      if (debug_p)
+        fprintf (stderr, "%s: mipmap %d x %d\n",
+                 progname, ximage->width, ximage->height);
+
+      gluBuild2DMipmaps (GL_TEXTURE_2D, 3, ximage->width, ximage->height,
+                         format, type, ximage->data);
+      err = glGetError();
+    }
+  else
+    {
+      /* glTexImage2D() requires the texture sizes to be powers of 2.
+         So first, create a texture of that size (but don't write any
+         data into it.)
+       */
+      tex_width  = to_pow2 (ximage->width);
+      tex_height = to_pow2 (ximage->height);
+
+      if (debug_p)
+        fprintf (stderr, "%s: texture %d x %d (%d x %d)\n",
+                 progname, ximage->width, ximage->height,
+                 tex_width, tex_height);
+
+      glTexImage2D (GL_TEXTURE_2D, 0, 3, tex_width, tex_height, 0,
+                    format, type, 0);
+      err = glGetError();
+
+      /* Now load our non-power-of-2 image data into the existing texture. */
+      if (!err)
+        {
+          glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0,
+                           ximage->width, ximage->height,
+                           GL_RGBA, GL_UNSIGNED_BYTE, ximage->data);
+          err = glGetError();
+        }
+    }
+
+  if (err)
+    {
+      char buf[100];
+      const char *s = (char *) gluErrorString (err);
+
+      if (!s || !*s)
+        {
+          sprintf (buf, "unknown error %d", err);
+          s = buf;
+        }
+
+      while (glGetError() != GL_NO_ERROR)
+        ;  /* clear any lingering errors */
+
+      if (++err_count > max_reduction)
+        {
+          fprintf (stderr,
+                   "\n"
+                   "%s: %dx%d texture failed, even after reducing to %dx%d:\n"
+                   "%s: The error was: \"%s\".\n"
+                   "%s: probably this means "
+                   "\"your video card is worthless and weak\"?\n\n",
+                   progname, orig_width, orig_height,
+                   ximage->width, ximage->height,
+                   progname, s,
+                   progname);
+          return False;
+        }
+      else
+        {
+          if (debug_p)
+            fprintf (stderr, "%s: mipmap error (%dx%d): %s\n",
+                     progname, ximage->width, ximage->height, s);
+          halve_image (ximage);
+          goto AGAIN;
+        }
+    }
+
+  if (width_return)  *width_return  = tex_width;
+  if (height_return) *height_return = tex_height;
+  return True;
+}
+
+
+static void screen_to_texture_async_cb (Screen *screen,
+                                        Window window, Drawable drawable,
+                                        const char *name, XRectangle *geometry,
+                                        void *closure);
+
+
+/* Grabs an image of the desktop (or another random image file) and
+   loads tht image into GL's texture memory.
+   Writes to stderr and returns False on error.
+ */
+Bool
+screen_to_texture (Screen *screen, Window window,
+                   int desired_width, int desired_height,
+                   Bool mipmap_p,
+                   char **filename_return,
+                   XRectangle *geometry_return,
+                   int *image_width_return,
+                   int *image_height_return,
+                   int *texture_width_return,
+                   int *texture_height_return)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  img_closure *data = (img_closure *) calloc (1, sizeof(*data));
+  XWindowAttributes xgwa;
+  char *filename = 0;
+  XRectangle geom = { 0, 0, 0, 0 };
+  int wret;
+
+  if (! image_width_return)
+    image_width_return = &wret;
+
+  if (debug_p)
+    data->load_time = double_time();
+
+  data->texid     = -1;
+  data->mipmap_p  = mipmap_p;
+  data->filename_return       = filename_return;
+  data->geometry_return       = geometry_return;
+  data->image_width_return    = image_width_return;
+  data->image_height_return   = image_height_return;
+  data->texture_width_return  = texture_width_return;
+  data->texture_height_return = texture_height_return;
+
+  XGetWindowAttributes (dpy, window, &xgwa);
+  data->pix_width  = xgwa.width;
+  data->pix_height = xgwa.height;
+  data->pix_depth  = xgwa.depth;
+
+  if (desired_width  && desired_width  < xgwa.width)
+    data->pix_width  = desired_width;
+  if (desired_height && desired_height < xgwa.height)
+    data->pix_height = desired_height;
+
+  data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
+                                data->pix_depth);
+  load_random_image (screen, window, data->pixmap, &filename, &geom);
+  screen_to_texture_async_cb (screen, window, data->pixmap, filename, &geom,
+                              data);
+
+  return (*image_width_return != 0);
+}
+
+
+/* Like the above, but the image is loaded in a background process,
+   and a callback is run when the loading is complete.
+   When the callback is called, the image data will have been loaded
+   into texture number `texid' (via glBindTexture.)
+
+   If an error occurred, width/height will be 0.
+ */
 void
-fork_screen_to_ximage (Screen *screen, Window window,
-                       void (*callback) (Screen *, Window, XImage *,
-                                         const char *name,
-                                         void *closure,
-                                         double cvt_time),
-                       void *closure)
+screen_to_texture_async (Screen *screen, Window window,
+                         int desired_width, int desired_height,
+                         Bool mipmap_p,
+                         GLuint texid,
+                         void (*callback) (const char *filename,
+                                           XRectangle *geometry,
+                                           int image_width,
+                                           int image_height,
+                                           int texture_width,
+                                           int texture_height,
+                                           void *closure),
+                         void *closure)
 {
   Display *dpy = DisplayOfScreen (screen);
   XWindowAttributes xgwa;
   img_closure *data = (img_closure *) calloc (1, sizeof(*data));
-  data->callback = callback;
-  data->closure  = closure;
+
+  if (debug_p)
+    data->load_time = double_time();
+
+  data->texid     = texid;
+  data->mipmap_p  = mipmap_p;
+  data->callback  = callback;
+  data->closure   = closure;
 
   XGetWindowAttributes (dpy, window, &xgwa);
-  data->pixmap = XCreatePixmap (dpy, window, xgwa.width, xgwa.height,
-                                xgwa.depth);
-  fork_load_random_image (screen, window, data->pixmap, img_cb, data);
+  data->pix_width  = xgwa.width;
+  data->pix_height = xgwa.height;
+  data->pix_depth  = xgwa.depth;
+
+  if (desired_width  && desired_width  < xgwa.width)
+    data->pix_width  = desired_width;
+  if (desired_height && desired_height < xgwa.height)
+    data->pix_height = desired_height;
+
+  data->pixmap = XCreatePixmap (dpy, window, data->pix_width, data->pix_height,
+                                data->pix_depth);
+  fork_load_random_image (screen, window, data->pixmap,
+                          screen_to_texture_async_cb, data);
+}
+
+
+/* Once we have an XImage, this loads it into GL.
+   This is used in both synchronous and asynchronous mode.
+ */
+static void
+screen_to_texture_async_cb (Screen *screen, Window window, Drawable drawable,
+                            const char *name, XRectangle *geometry,
+                            void *closure)
+{
+  Display *dpy = DisplayOfScreen (screen);
+  Bool ok;
+  XImage *ximage;
+  GLint type, format;
+  int iw=0, ih=0, tw=0, th=0;
+  double cvt_time=0, tex_time=0, done_time=0;
+  img_closure *data = (img_closure *) closure;
+  /* copy closure data to stack and free the original before running cb */
+  img_closure dd = *data;
+  memset (data, 0, sizeof (*data));
+  free (data);
+  data = 0;
+
+  if (geometry->width <= 0 || geometry->height <= 0)
+    {
+      /* This can happen if an old version of xscreensaver-getimage
+         is installed. */
+      geometry->x = 0;
+      geometry->y = 0;
+      geometry->width  = dd.pix_width;
+      geometry->height = dd.pix_height;
+    }
+
+  if (geometry->width <= 0 || geometry->height <= 0)
+    abort();
+
+  if (debug_p)
+    cvt_time = double_time();
+
+# ifdef REFORMAT_IMAGE_DATA
+  ximage = pixmap_to_gl_ximage (screen, window, dd.pixmap);
+  format = GL_RGBA;
+  type = GL_UNSIGNED_BYTE;
+
+#else /* ! REFORMAT_IMAGE_DATA */
+  {
+    Visual *visual = DefaultVisualOfScreen (screen);
+    GLint swap;
+
+    ximage = XCreateImage (dpy, visual, dd.pix_depth, ZPixmap, 0, 0,
+                           dd.pix_width, dd.pix_height, 32, 0);
+
+    /* Note: height+2 in "to" to be to work around an array bounds overrun
+       in gluBuild2DMipmaps / gluScaleImage. */
+    ximage->data = (char *) calloc (ximage->height+2, ximage->bytes_per_line);
+
+    if (!ximage->data ||
+        !XGetSubImage (dpy, dd.pixmap, 0, 0, ximage->width, ximage->height,
+                       ~0L, ximage->format, ximage, 0, 0))
+      {
+        XDestroyImage (ximage);
+        ximage = 0;
+      }
+
+    gl_settings_for_ximage (ximage, &type, &format, &swap);
+    glPixelStorei (GL_UNPACK_SWAP_BYTES, !swap);
+  }
+#endif /* REFORMAT_IMAGE_DATA */
+
+  XFreePixmap (dpy, dd.pixmap);
+  dd.pixmap = 0;
+
+  if (debug_p)
+    tex_time = double_time();
+
+  if (! ximage)
+    ok = False;
+  else
+    {
+      iw = ximage->width;
+      ih = ximage->height;
+      if (dd.texid != -1)
+        glBindTexture (GL_TEXTURE_2D, dd.texid);
+
+      glPixelStorei (GL_UNPACK_ALIGNMENT, ximage->bitmap_pad / 8);
+      ok = ximage_to_texture (ximage, type, format, &tw, &th, dd.mipmap_p);
+    }
+
+  if (ximage) XDestroyImage (ximage);
+
+  if (! ok)
+    iw = ih = tw = th = 0;
+
+  if (debug_p)
+    done_time = double_time();
+
+  if (debug_p)
+    fprintf (stderr,
+             /* prints: A + B + C = D
+                A = file I/O time (happens in background)
+                B = time to pull bits from server (this process)
+                C = time to convert bits to GL textures (this process)
+                D = total elapsed time from "want image" to "see image"
+
+                B+C is responsible for any frame-rate glitches.
+              */
+             "%s: loading elapsed: %.2f + %.2f + %.2f = %.2f sec\n",
+             progname,
+             cvt_time  - dd.load_time,
+             tex_time  - cvt_time,
+             done_time - tex_time,
+             done_time - dd.load_time);
+
+  if (dd.callback)
+    /* asynchronous mode */
+    dd.callback (name, geometry, iw, ih, tw, th, dd.closure);
+  else
+    {
+      /* synchronous mode */
+      if (dd.filename_return)       *dd.filename_return       = (char *) name;
+      if (dd.geometry_return)       *dd.geometry_return       = *geometry;
+      if (dd.image_width_return)    *dd.image_width_return    = iw;
+      if (dd.image_height_return)   *dd.image_height_return   = ih;
+      if (dd.texture_width_return)  *dd.texture_width_return  = tw;
+      if (dd.texture_height_return) *dd.texture_height_return = th;
+    }
 }