]> git.hungrycats.org Git - xscreensaver/blobdiff - jwxyz/jwxyz-android.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / jwxyz / jwxyz-android.c
index 6559b69be3b66a54edc117428bfeee876d13e5b2..ad7ec554f8f1a09da438d166fd9ae990dd27e3a7 100644 (file)
@@ -1,4 +1,4 @@
-/* xscreensaver, Copyright (c) 2016-2017 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright © 2016-2021 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
@@ -7,16 +7,20 @@
  * documentation.  No representations are made about the suitability of this
  * software for any purpose.  It is provided "as is" without express or 
  * implied warranty.
- *
- * This file is three related things:
- *
- *   - It is the Android-specific C companion to jwxyz-gl.c;
- *   - It is how C calls into Java to do things that OpenGL does not
- *     have access to without Java-based APIs;
- *   - It is how the jwxyz.java class calls into C to run the hacks.
  */
 
-#include "config.h"
+/* JWXYZ Is Not Xlib.
+
+   See the comment at the top of jwxyz-common.c for an explanation of
+   the division of labor between these various modules.
+
+   This file is three related things:
+  
+     - It is the Android-specific companion to jwxyz-gl.c or jwxyz-image.c;
+     - It is how C calls into Java to do things that OpenGL does not have
+       access to without Java-based APIs;
+     - It is how the jwxyz.java class calls into C to run the hacks.
+ */
 
 #ifdef HAVE_ANDROID /* whole file */
 
 #include <math.h>
 #include <setjmp.h>
 
+#define GL_GLEXT_PROTOTYPES
+
 #include <GLES/gl.h>
+#include <GLES/glext.h>
+#ifdef HAVE_GLES3
+# include <GLES3/gl3.h>
+#endif
 #include <jni.h>
+#include <android/bitmap.h>
 #include <android/log.h>
+#include <android/native_window_jni.h>
 #include <pthread.h>
 
 #include "screenhackI.h"
 #include "jwzglesI.h"
 #include "jwxyz-android.h"
 #include "textclient.h"
-#include "grabscreen.h"
+#include "grabclient.h"
 #include "pow2.h"
 
 
+#undef countof
 #define countof(x) (sizeof(x)/sizeof(*(x)))
 
 extern struct xscreensaver_function_table *xscreensaver_function_table;
@@ -48,7 +61,6 @@ extern struct xscreensaver_function_table *xscreensaver_function_table;
 struct function_table_entry {
   const char *progname;
   struct xscreensaver_function_table *xsft;
-  int api;
 };
 
 #include "gen/function-table.h"
@@ -160,30 +172,34 @@ create_pixmap (Window win, Drawable p)
   Assert (p->frame.width, "p->frame.width");
   Assert (p->frame.height, "p->frame.height");
 
-  struct running_hack *rh = win->window.rh;
+  if (win->window.rh->jwxyz_gl_p) {
+    struct running_hack *rh = win->window.rh;
 
-  if (rh->gl_fbo_p) {
-    glGenTextures (1, &p->texture);
-    glBindTexture (GL_TEXTURE_2D, p->texture);
+    if (rh->gl_fbo_p) {
+      glGenTextures (1, &p->texture);
+      glBindTexture (GL_TEXTURE_2D, p->texture);
 
-    glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
-                  to_pow2(p->frame.width), to_pow2(p->frame.height),
-                  0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+      glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
+                    to_pow2(p->frame.width), to_pow2(p->frame.height),
+                    0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
 
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
-    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+      glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+    } else {
+      EGLint attribs[5];
+      attribs[0] = EGL_WIDTH;
+      attribs[1] = p->frame.width;
+      attribs[2] = EGL_HEIGHT;
+      attribs[3] = p->frame.height;
+      attribs[4] = EGL_NONE;
+
+      p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
+                                             attribs);
+      Assert (p->egl_surface != EGL_NO_SURFACE,
+              "XCreatePixmap: got EGL_NO_SURFACE");
+    }
   } else {
-    EGLint attribs[5];
-    attribs[0] = EGL_WIDTH;
-    attribs[1] = p->frame.width;
-    attribs[2] = EGL_HEIGHT;
-    attribs[3] = p->frame.height;
-    attribs[4] = EGL_NONE;
-
-    p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
-                                           attribs);
-    Assert (p->egl_surface != EGL_NO_SURFACE,
-            "XCreatePixmap: got EGL_NO_SURFACE");
+    p->image_data = malloc (p->frame.width * p->frame.height * 4);
   }
 }
 
@@ -191,33 +207,232 @@ create_pixmap (Window win, Drawable p)
 static void
 free_pixmap (struct running_hack *rh, Pixmap p)
 {
-  if (rh->gl_fbo_p) {
-    glDeleteTextures (1, &p->texture);
+  if (rh->jwxyz_gl_p) {
+    if (rh->gl_fbo_p) {
+      glDeleteTextures (1, &p->texture);
+    } else {
+      eglDestroySurface(rh->egl_display, p->egl_surface);
+    }
   } else {
-    eglDestroySurface(rh->egl_display, p->egl_surface);
+    free (p->image_data);
   }
 }
 
 
 static void
-restore_surface (struct running_hack *rh)
+prepare_context (struct running_hack *rh)
 {
-  /* This is necessary because GLSurfaceView (in Java) will call
-     eglSwapBuffers under the (ordinarily reasonable) assumption that the EGL
-     surface associated with the EGL context hasn't been changed.
-   */
-  eglMakeCurrent (rh->egl_display, rh->egl_surface, rh->egl_surface,
-                  rh->egl_ctx);
-  rh->current_drawable = NULL;
+  if (rh->egl_p) {
+    /* TODO: Adreno recommends against doing this every frame. */
+    Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
+                           rh->egl_ctx),
+            "eglMakeCurrent failed");
+  }
+
+    /* Don't set matrices here; set them when an Xlib call triggers
+       jwxyz_bind_drawable/jwxyz_set_matrices.
+     */
+  if (rh->jwxyz_gl_p)
+    rh->current_drawable = NULL;
+
+  if (rh->xsft->visual == GL_VISUAL)
+    jwzgles_make_current (rh->gles_state);
 }
 
 
 static void
-get_egl_surface (struct running_hack *rh)
+get_egl_config_android(Window window, EGLDisplay *egl_display,
+                       EGLConfig *egl_config)
 {
-  rh->egl_surface = eglGetCurrentSurface(EGL_DRAW);
-  Assert(eglGetCurrentSurface(EGL_READ) == rh->egl_surface,
-         "doinit: EGL_READ != EGL_DRAW");
+# define R EGL_RED_SIZE
+# define G EGL_GREEN_SIZE
+# define B EGL_BLUE_SIZE
+# define A EGL_ALPHA_SIZE
+# define D EGL_DEPTH_SIZE
+# define I EGL_BUFFER_SIZE
+# define ST EGL_STENCIL_SIZE
+  EGLint templates[][40] = {
+    { R,8, G,8, B,8, A,8, D,8, ST,1, EGL_NONE }, /* rgba stencil */
+    { R,8, G,8, B,8,      D,8, ST,1, EGL_NONE }, /* rgb  stencil */
+    { R,4, G,4, B,4,      D,4, ST,1, EGL_NONE },
+    { R,2, G,2, B,2,      D,2, ST,1, EGL_NONE },
+    { R,8, G,8, B,8, A,8, D,8,       EGL_NONE }, /* rgba */
+    { R,8, G,8, B,8,      D,8,       EGL_NONE }, /* rgb  */
+    { R,4, G,4, B,4,      D,4,       EGL_NONE },
+    { R,2, G,2, B,2,      D,2,       EGL_NONE },
+    { R,1, G,1, B,1,      D,1,       EGL_NONE }  /* monochrome */
+  };
+  EGLint attrs[40];
+  EGLint nconfig;
+  int i, j, k, iter, pass;
+
+  char *glsls = get_string_resource_window (window, "prefersGLSL");
+  Bool glslp = (glsls && !strcasecmp(glsls, "true"));
+  iter = (glslp ? 2 : 1);
+
+  *egl_config = 0;
+  for (pass = 0; pass < iter; pass++)
+    {
+      for (i = 0; i < countof(templates); i++)
+        {
+          for (j = 0, k = 0; templates[i][j] != EGL_NONE; j += 2)
+            {
+              attrs[k++] = templates[i][j];
+              attrs[k++] = templates[i][j+1];
+            }
+
+          attrs[k++] = EGL_RENDERABLE_TYPE;
+# ifdef HAVE_GLES3
+          if (glslp && pass == 0)
+            attrs[k++] = EGL_OPENGL_ES3_BIT;
+          else
+            attrs[k++] = EGL_OPENGL_ES_BIT;
+# else
+          attrs[k++] = EGL_OPENGL_ES_BIT;
+# endif
+
+          attrs[k++] = EGL_NONE;
+
+          nconfig = -1;
+          if (eglChooseConfig (egl_display, attrs, egl_config, 1, &nconfig)
+              && nconfig == 1)
+            break;
+        }
+      if (i < countof(templates))
+        break;
+    }
+  Assert (*egl_config != 0, "no EGL config chosen");
+#if 1
+  {
+    int i;
+    const struct { int hexp; EGLint i; const char *s; } fields[] = {
+      { 1, EGL_CONFIG_ID,              "config ID:"     },
+      { 1, EGL_CONFIG_CAVEAT,          "caveat:"        },
+      { 1, EGL_CONFORMANT,             "conformant:"    },
+      { 0, EGL_COLOR_BUFFER_TYPE,      "buffer type:"   },
+      { 0, EGL_RED_SIZE,               "color size:"    },
+      { 0, EGL_TRANSPARENT_RED_VALUE,  "transparency:"  },
+      { 0, EGL_BUFFER_SIZE,            "buffer size:"   },
+      { 0, EGL_DEPTH_SIZE,             "depth size:"    },
+      { 0, EGL_LUMINANCE_SIZE, "lum size:"      },
+      { 0, EGL_STENCIL_SIZE,           "stencil size:"  },
+      { 0, EGL_ALPHA_MASK_SIZE,        "alpha mask:"    },
+      { 0, EGL_LEVEL,                  "level:"         },
+      { 0, EGL_SAMPLES,                "samples:"       },
+      { 0, EGL_SAMPLE_BUFFERS, "sample bufs:"   },
+      { 0, EGL_NATIVE_RENDERABLE,      "native render:" },
+      { 1, EGL_NATIVE_VISUAL_TYPE,     "native type:"   },
+      { 1, EGL_RENDERABLE_TYPE,        "render type:"   },
+      { 0, EGL_SURFACE_TYPE,           "surface type:"  },
+      { 0, EGL_BIND_TO_TEXTURE_RGB,    "bind RGB:"      },
+      { 0, EGL_BIND_TO_TEXTURE_RGBA,   "bind RGBA:"     },
+      { 0, EGL_MAX_PBUFFER_WIDTH,      "buffer width:"  },
+      { 0, EGL_MAX_PBUFFER_HEIGHT,     "buffer height:" },
+      { 0, EGL_MAX_PBUFFER_PIXELS,     "buffer pixels:" },
+      { 0, EGL_MAX_SWAP_INTERVAL,      "max swap:"      },
+      { 0, EGL_MIN_SWAP_INTERVAL,      "min swap:"      },
+    };
+    EGLint r=0, g=0, b=0, a=0, tt=0, tr=0, tg=0, tb=0;
+    eglGetConfigAttrib (egl_display, *egl_config, EGL_RED_SIZE,   &r);
+    eglGetConfigAttrib (egl_display, *egl_config, EGL_GREEN_SIZE, &g);
+    eglGetConfigAttrib (egl_display, *egl_config, EGL_BLUE_SIZE,  &b);
+    eglGetConfigAttrib (egl_display, *egl_config, EGL_ALPHA_SIZE, &a);
+    eglGetConfigAttrib (egl_display, *egl_config,
+                        EGL_TRANSPARENT_TYPE, &tt);
+    eglGetConfigAttrib (egl_display, *egl_config,
+                        EGL_TRANSPARENT_RED_VALUE,  &tr);
+    eglGetConfigAttrib (egl_display, *egl_config,
+                        EGL_TRANSPARENT_GREEN_VALUE,&tg);
+    eglGetConfigAttrib (egl_display, *egl_config,
+                        EGL_TRANSPARENT_BLUE_VALUE, &tb);
+    for (i = 0; i < countof(fields); i++)
+      {
+        EGLint v = 0;
+        char s[100];
+        eglGetConfigAttrib (egl_display, *egl_config, fields[i].i, &v);
+        if (fields[i].i == EGL_RED_SIZE)
+          sprintf (s, "%d, %d, %d, %d", r, g, b, a);
+        else if (fields[i].i == EGL_TRANSPARENT_RED_VALUE && tt != EGL_NONE)
+          sprintf (s, "%d, %d, %d", tr, tg, tb);
+        else if (fields[i].i == EGL_CONFIG_CAVEAT)
+          strcpy (s, (v == EGL_NONE ? "none" :
+                      v == EGL_SLOW_CONFIG ? "slow" :
+# ifdef EGL_NON_CONFORMANT
+                      v == EGL_NON_CONFORMANT ? "non-conformant" :
+# endif
+                      "???"));
+        else if (fields[i].i == EGL_COLOR_BUFFER_TYPE)
+          strcpy (s, (v == EGL_RGB_BUFFER ? "RGB" :
+                      v == EGL_LUMINANCE_BUFFER ? "luminance" :
+                      "???"));
+        else if (fields[i].i == EGL_CONFORMANT ||
+                 fields[i].i == EGL_RENDERABLE_TYPE)
+          {
+            sprintf (s, "0x%02x", v);
+            if (v & EGL_OPENGL_BIT)     strcat (s, " OpenGL");
+            if (v & EGL_OPENGL_ES_BIT)  strcat (s, " GLES-1.x");
+            if (v & EGL_OPENGL_ES2_BIT) strcat (s, " GLES-2.0");
+# ifdef EGL_OPENGL_ES3_BIT
+            if (v & EGL_OPENGL_ES3_BIT) strcat (s, " GLES-3.0");
+# endif
+            if (v & EGL_OPENVG_BIT)     strcat (s, " OpenVG");
+          }
+        else if (fields[i].hexp)
+          sprintf (s, "0x%02x", v);
+        else if (v)
+          sprintf (s, "%d", v);
+        else
+          *s = 0;
+
+        if (*s) Log ("init:    EGL %-14s %s\n", fields[i].s, s);
+      }
+  }
+#endif
+}
+
+
+static void
+get_egl_context_android(Window window, EGLDisplay *egl_display,
+                        EGLConfig *egl_config, EGLContext *egl_context)
+{
+  EGLint context_attribs[][3] = {
+    { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE },
+    { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }
+  };
+  EGLint *attrs;
+  Bool glslp;
+  int pass, iter;
+
+# ifdef EGL_OPENGL_ES3_BIT
+  char *glsls = get_string_resource_window (window, "prefersGLSL");
+  glslp = (glsls && !strcasecmp(glsls, "true"));
+  if (glslp)
+    {
+      EGLint renderable_type;
+      eglGetConfigAttrib (egl_display, egl_config, EGL_RENDERABLE_TYPE,
+                          &renderable_type);
+      Bool gles3p = (renderable_type & EGL_OPENGL_ES3_BIT) != 0;
+      glslp = glslp && gles3p;
+    }
+# else
+  glslp = False;
+# endif
+  iter = (glslp ? 2 : 1);
+
+  *egl_context = EGL_NO_CONTEXT;
+  for (pass = 0; pass < iter; pass++)
+    {
+      if (glslp && pass == 0)
+        attrs = context_attribs[1];
+      else
+        attrs = context_attribs[0];
+      *egl_context = eglCreateContext (egl_display, egl_config,
+                                       EGL_NO_CONTEXT, attrs);
+      if (*egl_context != EGL_NO_CONTEXT)
+        break;
+    }
+
+  Assert (*egl_context != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
 }
 
 
@@ -226,13 +441,12 @@ get_egl_surface (struct running_hack *rh)
 static void
 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
         const struct function_table_entry *chosen,
-        jobject defaults, jint w, jint h)
+        jobject defaults, jint w, jint h, jobject jni_surface)
 {
   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
 
   progname = chosen->progname;
   rh->xsft = chosen->xsft;
-  rh->api = chosen->api;
   rh->jni_env = env;
   rh->jobject = jwxyz_obj;  // update this every time we call into C
 
@@ -335,110 +549,166 @@ doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
   (*env)->DeleteLocalRef (env, c);
   if ((*env)->ExceptionOccurred(env)) abort();
 
+
+  /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
+
+  /* jwxyz_gl_p controls which implementation of Pixmaps we are using.
+
+     - jwxyz-image.c implements them in CPU RAM, and is used for Android GL
+       hacks, and for kumppa, petri and slip, which are too slow otherwise.
+
+     - jwxyz-gl.c implements them in terms of OpenGL textures, and is used
+       for all other Android X11 hacks.
+
+     Why two implemementations of Pixmaps for Android?
+
+     - GL hacks don't tend to need much in the way of Xlib, and having a
+       GL context to render Xlib alongside a GL context for rendering GL
+       seemed like trouble.
+
+     - X11 hacks render to a GL context because hardware acceleration tends
+       to work well with Xlib geometric stuff.  Better framerates, lower
+       power.
+   */
+  rh->jwxyz_gl_p =
+    rh->xsft->visual == DEFAULT_VISUAL &&
+    strcmp (progname, "kumppa") &&
+    strcmp (progname, "petri") &&
+    strcmp (progname, "slip") &&
+    strcmp (progname, "testx11");
+
+  Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
+       rh->jwxyz_gl_p ? "GL" : "IMAGE");
+
+  rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL;
+
+  int egl_major = -1, egl_minor = -1;
+
+  if (rh->egl_p) {
   // GL init. Must come after resource processing.
 
-  rh->egl_ctx = eglGetCurrentContext();
-  Assert(rh->egl_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
+    rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
+    Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
 
-  get_egl_surface (rh);
+    Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
+            "eglInitialize failed");
 
-  rh->egl_display = eglGetCurrentDisplay();
-  Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
+    get_egl_config_android (rh->window, rh->egl_display, &rh->egl_config);
 
-  unsigned egl_major, egl_minor;
-  if (sscanf ((const char *)eglQueryString(rh->egl_display, EGL_VERSION),
-              "%u.%u", &egl_major, &egl_minor) < 2)
-  {
-    egl_major = 1;
-    egl_minor = 0;
-  }
+    get_egl_context_android(rh->window, rh->egl_display, rh->egl_config,
+                            &rh->egl_ctx);
 
-  EGLint config_attribs[3];
-  config_attribs[0] = EGL_CONFIG_ID;
-  eglQueryContext(rh->egl_display, rh->egl_ctx, EGL_CONFIG_ID,
-                  &config_attribs[1]);
-  config_attribs[2] = EGL_NONE;
-
-  EGLint num_config;
-  eglChooseConfig(rh->egl_display, config_attribs,
-                  &rh->egl_config, 1, &num_config);
-  Assert(num_config == 1, "no EGL config chosen");
-
-  const GLubyte *extensions = glGetString (GL_EXTENSIONS);
-  rh->gl_fbo_p = jwzgles_gluCheckExtension (
-    (const GLubyte *)"GL_OES_framebuffer_object", extensions);
-
-  if (rh->gl_fbo_p) {
-    PFNGLGENFRAMEBUFFERSOESPROC glGenFramebuffersOES =
-      (PFNGLGENFRAMEBUFFERSOESPROC)
-        eglGetProcAddress ("glGenFramebuffersOES");
-
-    rh->glBindFramebufferOES = (PFNGLBINDFRAMEBUFFEROESPROC)
-        eglGetProcAddress ("glBindFramebufferOES");
-    rh->glFramebufferTexture2DOES = (PFNGLFRAMEBUFFERTEXTURE2DOESPROC)
-      eglGetProcAddress ("glFramebufferTexture2DOES");
-
-    glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
-    Assert (!rh->fb_default, "default framebuffer not current framebuffer");
-    glGenFramebuffersOES (1, &rh->fb_pixmap);
-    wnd->texture = 0;
+    ANativeWindow *native_window =
+      ANativeWindow_fromSurface (env, jni_surface);
+
+    rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
+                                              native_window, NULL);
+    Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
+
+    ANativeWindow_release (native_window);
   } else {
-    wnd->egl_surface = rh->egl_surface;
+    rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
+
+    int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
+                                                   WINDOW_FORMAT_RGBX_8888);
+    if (result < 0) {
+      // Maybe check this earlier?
+      Log ("can't set format (%d), surface may be invalid.", result);
+      (*env)->ThrowNew (env,
+        (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
+        "Surface lost");
+
+      ANativeWindow_release (rh->native_window);
+      rh->native_window = NULL;
+      return;
+    }
   }
 
-  /* TODO: Maybe ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT on the Java side of
-           things via EGLConfigChooser. I (Dave) seem to automatically get
-           this (n = 1), but maybe other devices won't.
-   */
-  rh->frontbuffer_p = False;
+  prepare_context (rh);
 
-  if (rh->api == API_XLIB ||
-      (rh->api == API_GL &&
-       strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
+  if (rh->egl_p) {
+    // GL_SHADING_LANGUAGE_VERSION undefined
+    Log ("init %s / %s / %s / EGL %d.%d",
+         glGetString (GL_VENDOR),
+         glGetString (GL_RENDERER),
+         glGetString (GL_VERSION),
+         egl_major, egl_minor);
+  }
 
-    rh->frontbuffer_p = True;
+  if (rh->jwxyz_gl_p) {
+    const GLubyte *extensions = glGetString (GL_EXTENSIONS);
+    rh->gl_fbo_p = jwzgles_gluCheckExtension (
+      (const GLubyte *)"GL_OES_framebuffer_object", extensions);
 
-# if 0 /* Might need to be 0 for Adreno...? */
-    if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
-      EGLint surface_type;
-      eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
-                         &surface_type);
-      if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
-        eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
-                         EGL_BUFFER_PRESERVED);
-        rh->frontbuffer_p = False;
-      }
+    if (rh->gl_fbo_p) {
+      glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
+      Assert (!rh->fb_default, "default framebuffer not current framebuffer");
+      glGenFramebuffersOES (1, &rh->fb_pixmap);
+      wnd->texture = 0;
+    } else {
+      wnd->egl_surface = rh->egl_surface;
     }
-# endif
 
-    if (rh->frontbuffer_p) {
-      /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
-      create_pixmap (wnd, wnd);
+    rh->frontbuffer_p = False;
 
-      /* No preserving backbuffers, so manual blit from back to "front". */
-      rh->frontbuffer.type = PIXMAP;
-      rh->frontbuffer.frame = wnd->frame;
-      rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
+    if (rh->xsft->visual == DEFAULT_VISUAL ||
+        (rh->xsft->visual == GL_VISUAL &&
+         strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
 
-      if(rh->gl_fbo_p) {
-        rh->frontbuffer.texture = 0;
-      } else {
-        Assert (wnd->egl_surface != rh->egl_surface,
-                "oops: wnd->egl_surface == rh->egl_surface");
-        rh->frontbuffer.egl_surface = rh->egl_surface;
+      rh->frontbuffer_p = True;
+
+# if 0 /* Might need to be 0 for Adreno...? */
+      if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
+        EGLint surface_type;
+        eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
+                           &surface_type);
+        if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
+          eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
+                           EGL_BUFFER_PRESERVED);
+          rh->frontbuffer_p = False;
+        }
+      }
+# endif
+
+      if (rh->frontbuffer_p) {
+        /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
+        create_pixmap (wnd, wnd);
+
+        /* No preserving backbuffers, so manual blit from back to "front". */
+        rh->frontbuffer.type = PIXMAP;
+        rh->frontbuffer.frame = wnd->frame;
+        rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
+
+        if(rh->gl_fbo_p) {
+          rh->frontbuffer.texture = 0;
+        } else {
+          Assert (wnd->egl_surface != rh->egl_surface,
+                  "oops: wnd->egl_surface == rh->egl_surface");
+          rh->frontbuffer.egl_surface = rh->egl_surface;
+        }
       }
     }
+
+    rh->dpy = jwxyz_gl_make_display(wnd);
+
+  } else {
+
+    create_pixmap (wnd, wnd);
+
+    static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
+    rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
+
   }
 
-  rh->dpy = jwxyz_make_display(wnd);
   Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
   // TODO: Zero looks right, but double-check that is the right number
 
   /* Requires valid rh->dpy. */
-  rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
-  rh->gles_state = jwzgles_make_state();
-  restore_surface(rh);
+  if (rh->jwxyz_gl_p)
+    rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
+
+  if (rh->xsft->visual == GL_VISUAL)
+    rh->gles_state = jwzgles_make_state();
  END: ;
 }
 
@@ -477,18 +747,24 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
 
   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
 
-  /* There is some kind of weird redisplay race condition between Settings
-     and the launching hack: e.g., Abstractile does XClearWindow at init,
-     but the screen is getting filled with random bits.  So let's wait a
-     few frames before really starting up.
-   */
-  if (++rh->frame_count < 8) {
-    /* glClearColor (1.0, 0.0, 1.0, 0.0); */
-    glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
-    goto END;
-  }
+  Window wnd = rh->window;
 
-  prepare_context(rh);
+  prepare_context (rh);
+
+  if (rh->egl_p) {
+    /* There is some kind of weird redisplay race condition between Settings
+       and the launching hack: e.g., Abstractile does XClearWindow at init,
+       but the screen is getting filled with random bits.  So let's wait a
+       few frames before really starting up.
+
+       TODO: Is this still true?
+     */
+    if (++rh->frame_count < 8) {
+      /* glClearColor (1.0, 0.0, 1.0, 0.0); */
+      glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
+      goto END;
+    }
+  }
 
 # ifdef DEBUG_FPS
   fps1 = double_time();
@@ -502,27 +778,28 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
     void *(*init_cb) (Display *, Window, void *) =
       (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
 
-    unsigned int bg =
-      get_pixel_resource (rh->dpy, 0, "background", "Background");
-    XSetWindowBackground (rh->dpy, rh->window, bg);
-    XClearWindow (rh->dpy, rh->window);
+    if (rh->xsft->visual == DEFAULT_VISUAL) {
+      unsigned int bg =
+        get_pixel_resource (rh->dpy, 0, "background", "Background");
+      XSetWindowBackground (rh->dpy, wnd, bg);
+      XClearWindow (rh->dpy, wnd);
+    }
 
-    rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg);
+    rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
     rh->initted_p = True;
 
     /* ignore_rotation_p doesn't quite work at the moment. */
     rh->ignore_rotation_p = False;
 /*
-      (rh->api == API_XLIB &&
+      (rh->xsft->visual == DEFAULT_VISUAL &&
        get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
 */
 
     if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
-      rh->fpst = fps_init (rh->dpy, rh->window);
+      rh->fpst = fps_init (rh->dpy, wnd);
       if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
     } else {
       rh->fpst = NULL;
-      rh->xsft->fps_cb = 0;
     }
 
     if ((*env)->ExceptionOccurred(env)) abort();
@@ -534,27 +811,65 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
 
   // Apparently events don't come in on the drawing thread, and JNI flips
   // out.  So we queue them there and run them here.
+  // TODO: Events should be coming in on the drawing thread now, so dump this.
   send_queued_events (rh);
 
 # ifdef DEBUG_FPS
   fps3 = double_time();
 # endif
 
-  delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
+  delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
+
+  if (rh->jwxyz_gl_p)
+    jwxyz_gl_flush (rh->dpy);
 
 # ifdef DEBUG_FPS
   fps4 = double_time();
 # endif
   if (rh->fpst && rh->xsft->fps_cb)
-    rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure);
+    rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
 
-  if (rh->frontbuffer_p) {
-    jwxyz_copy_area (rh->dpy, rh->window, &rh->frontbuffer, rh->copy_gc,
-                     0, 0, rh->window->frame.width, rh->window->frame.height,
-                     0, 0);
-  }
+  if (rh->egl_p) {
+    if (rh->jwxyz_gl_p && rh->frontbuffer_p) {
+      jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
+                          0, 0, wnd->frame.width, wnd->frame.height,
+                          0, 0);
+    }
+
+    // Getting failure here before/during/after resize, sometimes. Log sez:
+    // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
+    if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) {
+      Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)",
+           eglGetError());
+    }
+  } else {
+    ANativeWindow_Buffer buffer;
+    ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
+    int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
+    if (result) {
+      Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
+    } else {
+      /* Android can resize surfaces asynchronously. */
+      if (wnd->frame.width != buffer.width ||
+          wnd->frame.height != buffer.height) {
+        Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
+             buffer.width, buffer.height, buffer.format,
+             wnd->frame.width, wnd->frame.height);
+      }
 
-  restore_surface (rh);
+      Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
+              buffer.format == WINDOW_FORMAT_RGBX_8888,
+              "bad buffer format");
+
+      jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
+                  buffer.bits, buffer.stride * 4, 0, 0,
+                  MIN(wnd->frame.width, buffer.width),
+                  MIN(wnd->frame.height, buffer.height));
+      // TODO: Clear any area to sides and bottom.
+
+      ANativeWindow_unlockAndPost (rh->native_window);
+    }
+  }
 
  END: ;
 
@@ -603,7 +918,8 @@ acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
 JNIEXPORT void JNICALL
 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
                                             jstring jhack, jobject defaults,
-                                            jint w, jint h)
+                                            jint w, jint h,
+                                            jobject jni_surface)
 {
   pthread_mutex_lock(&mutg);
 
@@ -663,7 +979,8 @@ Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
 
   (*env)->ReleaseStringUTFChars(env, jhack, hack);
 
-  doinit (thiz, rh, env, &function_table[chosen], defaults, w, h);
+  doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
+          jni_surface);
 
   pthread_mutex_unlock(&mutg);
 }
@@ -682,14 +999,23 @@ Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
 
   struct running_hack *rh = getRunningHack(env, thiz);
 
+  prepare_context (rh);
+
+  if (rh->egl_p) {
+    glViewport (0, 0, w, h);
+  } else {
+    int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
+                                                   WINDOW_FORMAT_RGBX_8888);
+    if (result < 0)
+      Log ("failed to resize surface (%d)", result);
+  }
+
   Window wnd = rh->window;
   wnd->frame.x = 0;
   wnd->frame.y = 0;
   wnd->frame.width  = w;
   wnd->frame.height = h;
 
-  glViewport (0, 0, w, h);
-
   if (ignore_rotation_p(rh->dpy) &&
       rot != 0 && rot != 180 && rot != -180) {
     int swap = w;
@@ -699,29 +1025,33 @@ Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
     wnd->frame.height = h;
   }
 
-  get_egl_surface (rh);
-  if (rh->frontbuffer_p) {
+  if (rh->jwxyz_gl_p) {
+    if (rh->frontbuffer_p) {
+      free_pixmap (rh, wnd);
+      create_pixmap (wnd, wnd);
+
+      rh->frontbuffer.frame = wnd->frame;
+      if (!rh->gl_fbo_p)
+        rh->frontbuffer.egl_surface = rh->egl_surface;
+    }
+
+    jwxyz_window_resized (rh->dpy);
+  } else {
     free_pixmap (rh, wnd);
     create_pixmap (wnd, wnd);
-
-    rh->frontbuffer.frame = wnd->frame;
-    if (!rh->gl_fbo_p)
-      rh->frontbuffer.egl_surface = rh->egl_surface;
+    XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
   }
 
-  jwxyz_window_resized (rh->dpy);
   if (rh->initted_p)
     rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
                           wnd->frame.width, wnd->frame.height);
 
-  if (rh->api == API_GL) {
+  if (rh->xsft->visual == GL_VISUAL) {
     glMatrixMode (GL_PROJECTION);
     glRotatef (-rot, 0, 0, 1);
     glMatrixMode (GL_MODELVIEW);
   }
 
-  restore_surface (rh);
-
  END:
   pthread_mutex_unlock(&mutg);
 }
@@ -749,11 +1079,32 @@ Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
 
   prepare_context (rh);
 
+  if (rh->fpst)
+    rh->xsft->fps_free (rh->fpst);
   if (rh->initted_p)
     rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
-  XFreeGC (rh->dpy, rh->copy_gc);
-  jwzgles_free_state ();
-  jwxyz_free_display(rh->dpy);
+  if (rh->jwxyz_gl_p)
+    XFreeGC (rh->dpy, rh->copy_gc);
+  if (rh->xsft->visual == GL_VISUAL)
+    jwzgles_free_state ();
+
+  if (rh->jwxyz_gl_p)
+    jwxyz_gl_free_display(rh->dpy);
+  else
+    jwxyz_image_free_display(rh->dpy);
+
+  if (rh->egl_p) {
+    // eglDestroy* probably isn't necessary here.
+    eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
+                    EGL_NO_CONTEXT);
+    eglDestroySurface (rh->egl_display, rh->egl_surface);
+    eglDestroyContext (rh->egl_display, rh->egl_ctx);
+    eglTerminate (rh->egl_display);
+  } else {
+    free_pixmap (rh, rh->window);
+    if (rh->native_window)
+      ANativeWindow_release (rh->native_window);
+  }
 
   free(rh);
   (*env)->SetLongField(env, thiz, runningHackField, 0);
@@ -901,21 +1252,10 @@ Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
 }
 
 
-
 /***************************************************************************
   Backend functions for jwxyz-gl.c
  */
 
-void
-prepare_context (struct running_hack *rh)
-{
-  /* Don't set matrices here; set them when an Xlib call triggers
-     jwxyz_bind_drawable/jwxyz_set_matrices.
-   */
-  rh->current_drawable = NULL;
-  jwzgles_make_current (rh->gles_state);
-}
-
 static void
 finish_bind_drawable (Display *dpy, Drawable dst)
 {
@@ -929,11 +1269,11 @@ finish_bind_drawable (Display *dpy, Drawable dst)
 static void
 bind_drawable_fbo (struct running_hack *rh, Drawable d)
 {
-  rh->glBindFramebufferOES (GL_FRAMEBUFFER_OES,
-                            d->texture ? rh->fb_pixmap : rh->fb_default);
+  glBindFramebufferOES (GL_FRAMEBUFFER_OES,
+                        d->texture ? rh->fb_pixmap : rh->fb_default);
   if (d->texture) {
-    rh->glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
-                                   GL_TEXTURE_2D, d->texture, 0);
+    glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
+                               GL_TEXTURE_2D, d->texture, 0);
   }
 }
 
@@ -955,63 +1295,31 @@ jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
   }
 }
 
-
-const XRectangle *
-jwxyz_frame (Drawable d)
-{
-  return &d->frame;
-}
-
-
-unsigned int
-jwxyz_drawable_depth (Drawable d)
-{
-  return (d->type == WINDOW
-          ? visual_depth (NULL, NULL)
-          : d->pixmap.depth);
-}
-
-
-void
-jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
-{
-  xvpos->x = 0;
-  xvpos->y = 0;
-
-  if (xp) {
-    xp->x = w->window.last_mouse_x;
-    xp->y = w->window.last_mouse_y;
-  }
-}
-
-
-static void
-screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
-{
-  fps_compute (fpst, 0, -1);
-  fps_draw (fpst);
-}
-
-
 void
-jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
-                 int src_x, int src_y, unsigned int width, unsigned int height,
-                 int dst_x, int dst_y)
+jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
+                    int src_x, int src_y,
+                    unsigned int width, unsigned int height,
+                    int dst_x, int dst_y)
 {
   Window w = XRootWindow (dpy, 0);
   struct running_hack *rh = w->window.rh;
 
+  jwxyz_gl_flush (dpy);
+
   if (rh->gl_fbo_p && src->texture && src != dst) {
     bind_drawable_fbo (rh, dst);
     finish_bind_drawable (dpy, dst);
     rh->current_drawable = NULL;
 
+    jwxyz_gl_set_gc (dpy, gc);
+
     glBindTexture (GL_TEXTURE_2D, src->texture);
 
-    jwxyz_gl_draw_image (GL_TEXTURE_2D, to_pow2(src->frame.width),
+    jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width),
                          to_pow2(src->frame.height),
                          src_x, src->frame.height - src_y - height,
-                         width, height, dst_x, dst_y);
+                         jwxyz_drawable_depth (src), width, height,
+                         dst_x, dst_y, False);
     return;
   }
 
@@ -1033,8 +1341,9 @@ jwxyz_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
     bind_drawable_fbo (rh, dst);
   finish_bind_drawable (dpy, dst);
 
-  jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y, width, height,
-                                      dst_x, dst_y);
+  jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
+                                      jwxyz_drawable_depth (src),
+                                      width, height, dst_x, dst_y);
 
 #else
   // Kumppa: 0.17 FPS
@@ -1059,6 +1368,61 @@ jwxyz_assert_gl (void)
 }
 
 
+/***************************************************************************
+  Backend functions for jwxyz-image.c
+ */
+
+ptrdiff_t
+jwxyz_image_pitch (Drawable d)
+{
+  return d->frame.width * 4;
+}
+
+void *
+jwxyz_image_data (Drawable d)
+{
+  Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
+  return d->image_data;
+}
+
+
+const XRectangle *
+jwxyz_frame (Drawable d)
+{
+  return &d->frame;
+}
+
+
+unsigned int
+jwxyz_drawable_depth (Drawable d)
+{
+  return (d->type == WINDOW
+          ? visual_depth (NULL, NULL)
+          : d->pixmap.depth);
+}
+
+
+void
+jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
+{
+  xvpos->x = 0;
+  xvpos->y = 0;
+
+  if (xp) {
+    xp->x = w->window.last_mouse_x;
+    xp->y = w->window.last_mouse_y;
+  }
+}
+
+
+static void
+screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
+{
+  fps_compute (fpst, 0, -1);
+  fps_draw (fpst);
+}
+
+
 Pixmap
 XCreatePixmap (Display *dpy, Drawable d,
                unsigned int width, unsigned int height, unsigned int depth)
@@ -1079,9 +1443,11 @@ XCreatePixmap (Display *dpy, Drawable d,
   create_pixmap (win, p);
 
   /* For debugging. */
+# if 0
   jwxyz_bind_drawable (dpy, win, p);
   glClearColor (frand(1), frand(1), frand(1), 0);
   glClear (GL_COLOR_BUFFER_BIT);
+# endif
 
   return p;
 }
@@ -1091,8 +1457,13 @@ int
 XFreePixmap (Display *d, Pixmap p)
 {
   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
-  if (rh->current_drawable == p)
-    rh->current_drawable = NULL;
+
+  if (rh->jwxyz_gl_p) {
+    jwxyz_gl_flush (d);
+
+    if (rh->current_drawable == p)
+      rh->current_drawable = NULL;
+  }
 
   free_pixmap (rh, p);
   free (p);
@@ -1203,9 +1574,16 @@ textclient_mobile_url_string (Display *dpy, const char *url)
 float
 jwxyz_scale (Window main_window)
 {
+  // TODO: Use the actual device resolution.
   return 2;
 }
 
+float
+jwxyz_font_scale (Window main_window)
+{
+  return jwxyz_scale (main_window);
+}
+
 
 const char *
 jwxyz_default_font_family (int require)
@@ -1306,7 +1684,7 @@ static void dump_reference_tables(JNIEnv *env)
 //
 void
 jwxyz_render_text (Display *dpy, void *native_font,
-                   const char *str, size_t len, int utf8,
+                   const char *str, size_t len, Bool utf8, Bool antialias_p,
                    XCharStruct *cs, char **pixmap_ret)
 {
   Window window = RootWindow (dpy, 0);
@@ -1338,14 +1716,15 @@ jwxyz_render_text (Display *dpy, void *native_font,
   jstring jstr  = (*env)->NewStringUTF (env, s2);
   jclass      c = (*env)->GetObjectClass (env, obj);
   jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
-    "(Landroid/graphics/Paint;Ljava/lang/String;Z)Ljava/nio/ByteBuffer;");
+    "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
   if ((*env)->ExceptionOccurred(env)) abort();
   jobject buf =
     (m
      ? (*env)->CallObjectMethod (env, obj, m,
                                  (jobject) native_font,
                                  jstr,
-                                 (pixmap_ret ? JNI_TRUE : JNI_FALSE))
+                                 (pixmap_ret ? JNI_TRUE : JNI_FALSE),
+                                 antialias_p)
      : NULL);
   (*env)->DeleteLocalRef (env, c);
   (*env)->DeleteLocalRef (env, jstr);
@@ -1381,6 +1760,9 @@ jwxyz_render_text (Display *dpy, void *native_font,
 }
 
 
+// Returns the verbose Unicode name of this character, like "agrave" or
+// "daggerdouble".  Used by Unicrud, and by Fontglide with debugMetrics.
+//
 char *
 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
 {
@@ -1421,14 +1803,8 @@ jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
 
 /* Called from utils/grabclient.c */
 char *
-jwxyz_load_random_image (Display *dpy,
-                         int *width_ret, int *height_ret,
-                         char **name_ret)
+jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
 {
-
-  /* This function needs to be implemented for Android */
-  return 0;
-
   Window window = RootWindow (dpy, 0);
   struct running_hack *rh = window->window.rh;
   JNIEnv *env = rh->jni_env;
@@ -1453,43 +1829,109 @@ jwxyz_load_random_image (Display *dpy,
   jmethodID   m = (*env)->GetMethodID (env, c, 
                                        (grab_p
                                         ? "getScreenshot"
-                                        : "loadRandomImage"),
-                                       "(IIZ)Ljava/nio/ByteBuffer;");
+                                        : "checkThenLoadRandomImage"),
+                                       "(IIZ)[Ljava/lang/Object;");
+  if ((*env)->ExceptionOccurred(env)) abort();
+  jobjectArray img_name = (
+    m
+    ? (*env)->CallObjectMethod (env, obj, m,
+                                drawable->frame.width, drawable->frame.height,
+                                (rotate_p ? JNI_TRUE : JNI_FALSE))
+    : NULL);
   if ((*env)->ExceptionOccurred(env)) abort();
-  jobject buf = (m
-                 ? (*env)->CallObjectMethod (env, obj, m,
-                                             window->frame.width,
-                                             window->frame.height,
-                                             (rotate_p ? JNI_TRUE : JNI_FALSE))
-                 : NULL);
   (*env)->DeleteLocalRef (env, c);
 
-  unsigned char *bits = (unsigned char *)
-    (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
-
-  if (bits) {
-    int i = 0;
-    int L = (*env)->GetDirectBufferCapacity (env, buf);
-    if (L < 100) abort();
-    int width  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
-    int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
-    char *name = (char *) bits + i;
-    int L2 = strlen (name);
-    i += L2 + 1;
-    if (width * height * 4 != L - i) abort();
-    char *pix = malloc (L - i);
-    if (! pix) abort();
-    memcpy (pix, bits + i, L - i);
-    *width_ret  = width;
-    *height_ret = height;
-    *name_ret   = strdup (name);
-    return (char *) pix;
+  if (!img_name) {
+    fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
+    return NULL;
   }
 
-  if (buf)
-    (*env)->DeleteLocalRef (env, buf);
+  jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
 
-  return 0;
+  AndroidBitmapInfo bmp_info;
+  AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
+
+  XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
+                              ZPixmap, 0, NULL,
+                              bmp_info.width, bmp_info.height, 0,
+                              bmp_info.stride);
+
+  AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
+
+  XPutImage (dpy, drawable, gc, img, 0, 0,
+             (drawable->frame.width  - bmp_info.width) / 2,
+             (drawable->frame.height - bmp_info.height) / 2,
+             bmp_info.width, bmp_info.height);
+
+  AndroidBitmap_unlockPixels (env, jbitmap);
+  img->data = NULL;
+  XDestroyImage (img);
+
+  return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
+}
+
+
+XImage *
+jwxyz_png_to_ximage (Display *dpy, Visual *visual,
+                     const unsigned char *png_data, unsigned long data_size)
+{
+  Window window = RootWindow (dpy, 0);
+  struct running_hack *rh = window->window.rh;
+  JNIEnv *env = rh->jni_env;
+  jobject obj = rh->jobject;
+  jclass    c = (*env)->GetObjectClass (env, obj);
+  jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
+                                     "([B)Landroid/graphics/Bitmap;");
+  if ((*env)->ExceptionOccurred(env)) abort();
+  jbyteArray jdata = (*env)->NewByteArray (env, data_size);
+  (*env)->SetByteArrayRegion (env, jdata, 0,
+                              data_size, (const jbyte *) png_data);
+  jobject jbitmap = (
+    m
+    ? (*env)->CallObjectMethod (env, obj, m, jdata)
+    : NULL);
+  if ((*env)->ExceptionOccurred(env)) abort();
+  (*env)->DeleteLocalRef (env, c);
+  (*env)->DeleteLocalRef (env, jdata);
+  if (!jbitmap)
+    return NULL;
+
+  AndroidBitmapInfo bmp_info;
+  AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
+
+  XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
+                              bmp_info.width, bmp_info.height, 8,
+                              bmp_info.stride);
+  char *bits = 0;
+  AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
+  img->data = (char *) calloc (img->bytes_per_line, img->height);
+  memcpy (img->data, bits, img->bytes_per_line * img->height);
+  AndroidBitmap_unlockPixels (env, jbitmap);
+
+  // Java should have returned ARGB data.
+  // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
+  if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
+# ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
+#  error Need a __BYTE_ORDER__.
+# elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+  img->byte_order = img->bitmap_bit_order = LSBFirst;
+# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+  img->byte_order = img->bitmap_bit_order = MSBFirst;
+# else
+#  error Need a __BYTE_ORDER__.
+# endif
+
+  static const union {
+    uint8_t bytes[4];
+    uint32_t pixel;
+  } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
+    c2 = {{0x00, 0x00, 0xff, 0x00}};
+
+  img->red_mask   = c0.pixel;
+  img->green_mask = c1.pixel;
+  img->blue_mask  = c2.pixel;
+
+  return img;
 }
 
 #endif /* HAVE_ANDROID */