]> git.hungrycats.org Git - xscreensaver/blob - jwxyz/jwxyz-android.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / jwxyz / jwxyz-android.c
1 /* xscreensaver, Copyright © 2016-2021 Jamie Zawinski <jwz@jwz.org>
2  *
3  * Permission to use, copy, modify, distribute, and sell this software and its
4  * documentation for any purpose is hereby granted without fee, provided that
5  * the above copyright notice appear in all copies and that both that
6  * copyright notice and this permission notice appear in supporting
7  * documentation.  No representations are made about the suitability of this
8  * software for any purpose.  It is provided "as is" without express or 
9  * implied warranty.
10  */
11
12 /* JWXYZ Is Not Xlib.
13
14    See the comment at the top of jwxyz-common.c for an explanation of
15    the division of labor between these various modules.
16
17    This file is three related things:
18   
19      - It is the Android-specific companion to jwxyz-gl.c or jwxyz-image.c;
20      - It is how C calls into Java to do things that OpenGL does not have
21        access to without Java-based APIs;
22      - It is how the jwxyz.java class calls into C to run the hacks.
23  */
24
25 #ifdef HAVE_ANDROID /* whole file */
26
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <time.h>
31 #include <math.h>
32 #include <setjmp.h>
33
34 #define GL_GLEXT_PROTOTYPES
35
36 #include <GLES/gl.h>
37 #include <GLES/glext.h>
38 #ifdef HAVE_GLES3
39 # include <GLES3/gl3.h>
40 #endif
41 #include <jni.h>
42 #include <android/bitmap.h>
43 #include <android/log.h>
44 #include <android/native_window_jni.h>
45 #include <pthread.h>
46
47 #include "screenhackI.h"
48 #include "jwxyzI.h"
49 #include "jwzglesI.h"
50 #include "jwxyz-android.h"
51 #include "textclient.h"
52 #include "grabclient.h"
53 #include "pow2.h"
54
55
56 #undef countof
57 #define countof(x) (sizeof(x)/sizeof(*(x)))
58
59 extern struct xscreensaver_function_table *xscreensaver_function_table;
60
61 struct function_table_entry {
62   const char *progname;
63   struct xscreensaver_function_table *xsft;
64 };
65
66 #include "gen/function-table.h"
67
68 struct event_queue {
69   XEvent event;
70   struct event_queue *next;
71 };
72
73 static void send_queued_events (struct running_hack *rh);
74
75 const char *progname;
76 const char *progclass;
77 int mono_p = 0;
78
79 static JavaVM *global_jvm;
80 static jmp_buf jmp_target;
81
82 static double current_rotation = 0;
83
84 extern void check_gl_error (const char *type);
85
86 void
87 jwxyz_logv(Bool error, const char *fmt, va_list args)
88 {
89   __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
90                        "xscreensaver", fmt, args);
91
92   /* The idea here is that if the device/emulator dies shortly after a log
93      message, then waiting here for a short while should increase the odds
94      that adb logcat can pick up the message before everything blows up. Of
95      course, doing this means dumping a ton of messages will slow things down
96      significantly.
97   */
98 # if 0
99   struct timespec ts;
100   ts.tv_sec = 0;
101   ts.tv_nsec = 25 * 1000000;
102   nanosleep(&ts, NULL);
103 # endif
104 }
105
106 /* Handle an abort on Android
107    TODO: Test that Android handles aborts properly
108  */
109 void
110 jwxyz_abort (const char *fmt, ...)
111 {
112   /* Send error to Android device log */
113   if (!fmt || !*fmt)
114     fmt = "abort";
115
116   va_list args;
117   va_start (args, fmt);
118   jwxyz_logv(True, fmt, args);
119   va_end (args);
120
121   char buf[10240];
122   va_start (args, fmt);
123   vsprintf (buf, fmt, args);
124   va_end (args);
125
126   JNIEnv *env;
127   (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
128
129   if (! (*env)->ExceptionOccurred(env)) {
130     // If there's already an exception queued, let's just go with that one.
131     // Else, queue a Java exception to be thrown.
132     (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
133                       buf);
134   }
135
136   // Nonlocal exit out of the jwxyz code.
137   longjmp (jmp_target, 1);
138 }
139
140
141 /* We get to keep live references to Java classes in use because the VM can
142    unload a class that isn't being used, which invalidates field and method
143    IDs.
144    https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
145 */
146
147
148 // #### only need one var I think
149 static size_t classRefCount = 0;
150 static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
151     globalRefMapEntry;
152
153 static jfieldID runningHackField;
154 static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
155 static jmethodID entryGetKey, entryGetValue;
156
157 static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
158
159 static void screenhack_do_fps (Display *, Window, fps_state *, void *);
160 static char *get_string_resource_window (Window window, char *name);
161
162
163 /* Also creates double-buffered windows. */
164 static void
165 create_pixmap (Window win, Drawable p)
166 {
167   // See also:
168   // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
169   // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
170   // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
171
172   Assert (p->frame.width, "p->frame.width");
173   Assert (p->frame.height, "p->frame.height");
174
175   if (win->window.rh->jwxyz_gl_p) {
176     struct running_hack *rh = win->window.rh;
177
178     if (rh->gl_fbo_p) {
179       glGenTextures (1, &p->texture);
180       glBindTexture (GL_TEXTURE_2D, p->texture);
181
182       glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA,
183                     to_pow2(p->frame.width), to_pow2(p->frame.height),
184                     0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
185
186       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
187       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
188     } else {
189       EGLint attribs[5];
190       attribs[0] = EGL_WIDTH;
191       attribs[1] = p->frame.width;
192       attribs[2] = EGL_HEIGHT;
193       attribs[3] = p->frame.height;
194       attribs[4] = EGL_NONE;
195
196       p->egl_surface = eglCreatePbufferSurface(rh->egl_display, rh->egl_config,
197                                              attribs);
198       Assert (p->egl_surface != EGL_NO_SURFACE,
199               "XCreatePixmap: got EGL_NO_SURFACE");
200     }
201   } else {
202     p->image_data = malloc (p->frame.width * p->frame.height * 4);
203   }
204 }
205
206
207 static void
208 free_pixmap (struct running_hack *rh, Pixmap p)
209 {
210   if (rh->jwxyz_gl_p) {
211     if (rh->gl_fbo_p) {
212       glDeleteTextures (1, &p->texture);
213     } else {
214       eglDestroySurface(rh->egl_display, p->egl_surface);
215     }
216   } else {
217     free (p->image_data);
218   }
219 }
220
221
222 static void
223 prepare_context (struct running_hack *rh)
224 {
225   if (rh->egl_p) {
226     /* TODO: Adreno recommends against doing this every frame. */
227     Assert (eglMakeCurrent(rh->egl_display, rh->egl_surface, rh->egl_surface,
228                            rh->egl_ctx),
229             "eglMakeCurrent failed");
230   }
231
232     /* Don't set matrices here; set them when an Xlib call triggers
233        jwxyz_bind_drawable/jwxyz_set_matrices.
234      */
235   if (rh->jwxyz_gl_p)
236     rh->current_drawable = NULL;
237
238   if (rh->xsft->visual == GL_VISUAL)
239     jwzgles_make_current (rh->gles_state);
240 }
241
242
243 static void
244 get_egl_config_android(Window window, EGLDisplay *egl_display,
245                        EGLConfig *egl_config)
246 {
247 # define R EGL_RED_SIZE
248 # define G EGL_GREEN_SIZE
249 # define B EGL_BLUE_SIZE
250 # define A EGL_ALPHA_SIZE
251 # define D EGL_DEPTH_SIZE
252 # define I EGL_BUFFER_SIZE
253 # define ST EGL_STENCIL_SIZE
254   EGLint templates[][40] = {
255     { R,8, G,8, B,8, A,8, D,8, ST,1, EGL_NONE }, /* rgba stencil */
256     { R,8, G,8, B,8,      D,8, ST,1, EGL_NONE }, /* rgb  stencil */
257     { R,4, G,4, B,4,      D,4, ST,1, EGL_NONE },
258     { R,2, G,2, B,2,      D,2, ST,1, EGL_NONE },
259     { R,8, G,8, B,8, A,8, D,8,       EGL_NONE }, /* rgba */
260     { R,8, G,8, B,8,      D,8,       EGL_NONE }, /* rgb  */
261     { R,4, G,4, B,4,      D,4,       EGL_NONE },
262     { R,2, G,2, B,2,      D,2,       EGL_NONE },
263     { R,1, G,1, B,1,      D,1,       EGL_NONE }  /* monochrome */
264   };
265   EGLint attrs[40];
266   EGLint nconfig;
267   int i, j, k, iter, pass;
268
269   char *glsls = get_string_resource_window (window, "prefersGLSL");
270   Bool glslp = (glsls && !strcasecmp(glsls, "true"));
271   iter = (glslp ? 2 : 1);
272
273   *egl_config = 0;
274   for (pass = 0; pass < iter; pass++)
275     {
276       for (i = 0; i < countof(templates); i++)
277         {
278           for (j = 0, k = 0; templates[i][j] != EGL_NONE; j += 2)
279             {
280               attrs[k++] = templates[i][j];
281               attrs[k++] = templates[i][j+1];
282             }
283
284           attrs[k++] = EGL_RENDERABLE_TYPE;
285 # ifdef HAVE_GLES3
286           if (glslp && pass == 0)
287             attrs[k++] = EGL_OPENGL_ES3_BIT;
288           else
289             attrs[k++] = EGL_OPENGL_ES_BIT;
290 # else
291           attrs[k++] = EGL_OPENGL_ES_BIT;
292 # endif
293
294           attrs[k++] = EGL_NONE;
295
296           nconfig = -1;
297           if (eglChooseConfig (egl_display, attrs, egl_config, 1, &nconfig)
298               && nconfig == 1)
299             break;
300         }
301       if (i < countof(templates))
302         break;
303     }
304   Assert (*egl_config != 0, "no EGL config chosen");
305 #if 1
306   {
307     int i;
308     const struct { int hexp; EGLint i; const char *s; } fields[] = {
309       { 1, EGL_CONFIG_ID,               "config ID:"     },
310       { 1, EGL_CONFIG_CAVEAT,           "caveat:"        },
311       { 1, EGL_CONFORMANT,              "conformant:"    },
312       { 0, EGL_COLOR_BUFFER_TYPE,       "buffer type:"   },
313       { 0, EGL_RED_SIZE,                "color size:"    },
314       { 0, EGL_TRANSPARENT_RED_VALUE,   "transparency:"  },
315       { 0, EGL_BUFFER_SIZE,             "buffer size:"   },
316       { 0, EGL_DEPTH_SIZE,              "depth size:"    },
317       { 0, EGL_LUMINANCE_SIZE,  "lum size:"      },
318       { 0, EGL_STENCIL_SIZE,            "stencil size:"  },
319       { 0, EGL_ALPHA_MASK_SIZE, "alpha mask:"    },
320       { 0, EGL_LEVEL,                   "level:"         },
321       { 0, EGL_SAMPLES,         "samples:"       },
322       { 0, EGL_SAMPLE_BUFFERS,  "sample bufs:"   },
323       { 0, EGL_NATIVE_RENDERABLE,       "native render:" },
324       { 1, EGL_NATIVE_VISUAL_TYPE,      "native type:"   },
325       { 1, EGL_RENDERABLE_TYPE, "render type:"   },
326       { 0, EGL_SURFACE_TYPE,            "surface type:"  },
327       { 0, EGL_BIND_TO_TEXTURE_RGB,     "bind RGB:"      },
328       { 0, EGL_BIND_TO_TEXTURE_RGBA,    "bind RGBA:"     },
329       { 0, EGL_MAX_PBUFFER_WIDTH,       "buffer width:"  },
330       { 0, EGL_MAX_PBUFFER_HEIGHT,      "buffer height:" },
331       { 0, EGL_MAX_PBUFFER_PIXELS,      "buffer pixels:" },
332       { 0, EGL_MAX_SWAP_INTERVAL,       "max swap:"      },
333       { 0, EGL_MIN_SWAP_INTERVAL,       "min swap:"      },
334     };
335     EGLint r=0, g=0, b=0, a=0, tt=0, tr=0, tg=0, tb=0;
336     eglGetConfigAttrib (egl_display, *egl_config, EGL_RED_SIZE,   &r);
337     eglGetConfigAttrib (egl_display, *egl_config, EGL_GREEN_SIZE, &g);
338     eglGetConfigAttrib (egl_display, *egl_config, EGL_BLUE_SIZE,  &b);
339     eglGetConfigAttrib (egl_display, *egl_config, EGL_ALPHA_SIZE, &a);
340     eglGetConfigAttrib (egl_display, *egl_config,
341                         EGL_TRANSPARENT_TYPE, &tt);
342     eglGetConfigAttrib (egl_display, *egl_config,
343                         EGL_TRANSPARENT_RED_VALUE,  &tr);
344     eglGetConfigAttrib (egl_display, *egl_config,
345                         EGL_TRANSPARENT_GREEN_VALUE,&tg);
346     eglGetConfigAttrib (egl_display, *egl_config,
347                         EGL_TRANSPARENT_BLUE_VALUE, &tb);
348     for (i = 0; i < countof(fields); i++)
349       {
350         EGLint v = 0;
351         char s[100];
352         eglGetConfigAttrib (egl_display, *egl_config, fields[i].i, &v);
353         if (fields[i].i == EGL_RED_SIZE)
354           sprintf (s, "%d, %d, %d, %d", r, g, b, a);
355         else if (fields[i].i == EGL_TRANSPARENT_RED_VALUE && tt != EGL_NONE)
356           sprintf (s, "%d, %d, %d", tr, tg, tb);
357         else if (fields[i].i == EGL_CONFIG_CAVEAT)
358           strcpy (s, (v == EGL_NONE ? "none" :
359                       v == EGL_SLOW_CONFIG ? "slow" :
360 # ifdef EGL_NON_CONFORMANT
361                       v == EGL_NON_CONFORMANT ? "non-conformant" :
362 # endif
363                       "???"));
364         else if (fields[i].i == EGL_COLOR_BUFFER_TYPE)
365           strcpy (s, (v == EGL_RGB_BUFFER ? "RGB" :
366                       v == EGL_LUMINANCE_BUFFER ? "luminance" :
367                       "???"));
368         else if (fields[i].i == EGL_CONFORMANT ||
369                  fields[i].i == EGL_RENDERABLE_TYPE)
370           {
371             sprintf (s, "0x%02x", v);
372             if (v & EGL_OPENGL_BIT)     strcat (s, " OpenGL");
373             if (v & EGL_OPENGL_ES_BIT)  strcat (s, " GLES-1.x");
374             if (v & EGL_OPENGL_ES2_BIT) strcat (s, " GLES-2.0");
375 # ifdef EGL_OPENGL_ES3_BIT
376             if (v & EGL_OPENGL_ES3_BIT) strcat (s, " GLES-3.0");
377 # endif
378             if (v & EGL_OPENVG_BIT)     strcat (s, " OpenVG");
379           }
380         else if (fields[i].hexp)
381           sprintf (s, "0x%02x", v);
382         else if (v)
383           sprintf (s, "%d", v);
384         else
385           *s = 0;
386
387         if (*s) Log ("init:    EGL %-14s %s\n", fields[i].s, s);
388       }
389   }
390 #endif
391 }
392
393
394 static void
395 get_egl_context_android(Window window, EGLDisplay *egl_display,
396                         EGLConfig *egl_config, EGLContext *egl_context)
397 {
398   EGLint context_attribs[][3] = {
399     { EGL_CONTEXT_CLIENT_VERSION, 1, EGL_NONE },
400     { EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE }
401   };
402   EGLint *attrs;
403   Bool glslp;
404   int pass, iter;
405
406 # ifdef EGL_OPENGL_ES3_BIT
407   char *glsls = get_string_resource_window (window, "prefersGLSL");
408   glslp = (glsls && !strcasecmp(glsls, "true"));
409   if (glslp)
410     {
411       EGLint renderable_type;
412       eglGetConfigAttrib (egl_display, egl_config, EGL_RENDERABLE_TYPE,
413                           &renderable_type);
414       Bool gles3p = (renderable_type & EGL_OPENGL_ES3_BIT) != 0;
415       glslp = glslp && gles3p;
416     }
417 # else
418   glslp = False;
419 # endif
420   iter = (glslp ? 2 : 1);
421
422   *egl_context = EGL_NO_CONTEXT;
423   for (pass = 0; pass < iter; pass++)
424     {
425       if (glslp && pass == 0)
426         attrs = context_attribs[1];
427       else
428         attrs = context_attribs[0];
429       *egl_context = eglCreateContext (egl_display, egl_config,
430                                        EGL_NO_CONTEXT, attrs);
431       if (*egl_context != EGL_NO_CONTEXT)
432         break;
433     }
434
435   Assert (*egl_context != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
436 }
437
438
439 // Initialized OpenGL and runs the screenhack's init function.
440 //
441 static void
442 doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
443         const struct function_table_entry *chosen,
444         jobject defaults, jint w, jint h, jobject jni_surface)
445 {
446   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
447
448   progname = chosen->progname;
449   rh->xsft = chosen->xsft;
450   rh->jni_env = env;
451   rh->jobject = jwxyz_obj;  // update this every time we call into C
452
453   (*env)->GetJavaVM (env, &global_jvm);
454
455 # undef ya_rand_init  // This is the one and only place it is allowed
456   ya_rand_init (0);
457
458   Window wnd = (Window) calloc(1, sizeof(*wnd));
459   wnd->window.rh = rh;
460   wnd->frame.width = w;
461   wnd->frame.height = h;
462   wnd->type = WINDOW;
463
464   rh->window = wnd;
465   progclass = rh->xsft->progclass;
466
467   if ((*env)->ExceptionOccurred(env)) abort();
468
469   // This has to come before resource processing. It does not do graphics.
470   if (rh->xsft->setup_cb)
471     rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
472
473   if ((*env)->ExceptionOccurred(env)) abort();
474
475   // Load the defaults.
476   // Unceremoniously stolen from [PrefsReader defaultsToDict:].
477
478   jclass     c = (*env)->GetObjectClass (env, defaults);
479   jmethodID  m = (*env)->GetMethodID (env, c, "put",
480                  "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
481   if (! m) abort();
482   if ((*env)->ExceptionOccurred(env)) abort();
483
484   const struct { const char *key, *val; } default_defaults[] = {
485     { "doubleBuffer", "True" },
486     { "multiSample",  "False" },
487     { "texFontCacheSize", "30" },
488     { "textMode", "date" },
489     { "textURL",
490       "https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
491     { "grabDesktopImages",  "True" },
492     { "chooseRandomImages", "True" },
493   };
494
495   for (int i = 0; i < countof(default_defaults); i++) {
496     const char *key = default_defaults[i].key;
497     const char *val = default_defaults[i].val;
498     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
499     strcpy (key2, progname);
500     strcat (key2, "_");
501     strcat (key2, key);
502
503     // defaults.put(key2, val);
504     jstring jkey = (*env)->NewStringUTF (env, key2);
505     jstring jval = (*env)->NewStringUTF (env, val);
506     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
507     (*env)->DeleteLocalRef (env, jkey);
508     (*env)->DeleteLocalRef (env, jval);
509     // Log ("default0: \"%s\" = \"%s\"", key2, val);
510     free (key2);
511   }
512
513   const char *const *defs = rh->xsft->defaults;
514   while (*defs) {
515     char *line = strdup (*defs);
516     char *key, *val;
517     key = line;
518     while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
519       key++;
520     val = key;
521     while (*val && *val != ':')
522       val++;
523     if (*val != ':') abort();
524     *val++ = 0;
525     while (*val == ' ' || *val == '\t')
526       val++;
527
528     unsigned long L = strlen(val);
529     while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
530       val[--L] = 0;
531
532     char *key2 = malloc (strlen(progname) + strlen(key) + 2);
533     strcpy (key2, progname);
534     strcat (key2, "_");
535     strcat (key2, key);
536
537     // defaults.put(key2, val);
538     jstring jkey = (*env)->NewStringUTF (env, key2);
539     jstring jval = (*env)->NewStringUTF (env, val);
540     (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
541     (*env)->DeleteLocalRef (env, jkey);
542     (*env)->DeleteLocalRef (env, jval);
543     // Log ("default: \"%s\" = \"%s\"", key2, val);
544     free (key2);
545     free (line);
546     defs++;
547   }
548
549   (*env)->DeleteLocalRef (env, c);
550   if ((*env)->ExceptionOccurred(env)) abort();
551
552
553   /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
554
555   /* jwxyz_gl_p controls which implementation of Pixmaps we are using.
556
557      - jwxyz-image.c implements them in CPU RAM, and is used for Android GL
558        hacks, and for kumppa, petri and slip, which are too slow otherwise.
559
560      - jwxyz-gl.c implements them in terms of OpenGL textures, and is used
561        for all other Android X11 hacks.
562
563      Why two implemementations of Pixmaps for Android?
564
565      - GL hacks don't tend to need much in the way of Xlib, and having a
566        GL context to render Xlib alongside a GL context for rendering GL
567        seemed like trouble.
568
569      - X11 hacks render to a GL context because hardware acceleration tends
570        to work well with Xlib geometric stuff.  Better framerates, lower
571        power.
572    */
573   rh->jwxyz_gl_p =
574     rh->xsft->visual == DEFAULT_VISUAL &&
575     strcmp (progname, "kumppa") &&
576     strcmp (progname, "petri") &&
577     strcmp (progname, "slip") &&
578     strcmp (progname, "testx11");
579
580   Log ("init: %s @ %dx%d: using JWXYZ_%s", progname, w, h,
581        rh->jwxyz_gl_p ? "GL" : "IMAGE");
582
583   rh->egl_p = rh->jwxyz_gl_p || rh->xsft->visual == GL_VISUAL;
584
585   int egl_major = -1, egl_minor = -1;
586
587   if (rh->egl_p) {
588   // GL init. Must come after resource processing.
589
590     rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
591     Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
592
593     Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
594             "eglInitialize failed");
595
596     get_egl_config_android (rh->window, rh->egl_display, &rh->egl_config);
597
598     get_egl_context_android(rh->window, rh->egl_display, rh->egl_config,
599                             &rh->egl_ctx);
600
601     ANativeWindow *native_window =
602       ANativeWindow_fromSurface (env, jni_surface);
603
604     rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
605                                               native_window, NULL);
606     Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
607
608     ANativeWindow_release (native_window);
609   } else {
610     rh->native_window = ANativeWindow_fromSurface (env, jni_surface);
611
612     int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
613                                                    WINDOW_FORMAT_RGBX_8888);
614     if (result < 0) {
615       // Maybe check this earlier?
616       Log ("can't set format (%d), surface may be invalid.", result);
617       (*env)->ThrowNew (env,
618         (*env)->FindClass(env, "org/jwz/xscreensaver/jwxyz$SurfaceLost"),
619         "Surface lost");
620
621       ANativeWindow_release (rh->native_window);
622       rh->native_window = NULL;
623       return;
624     }
625   }
626
627   prepare_context (rh);
628
629   if (rh->egl_p) {
630     // GL_SHADING_LANGUAGE_VERSION undefined
631     Log ("init %s / %s / %s / EGL %d.%d",
632          glGetString (GL_VENDOR),
633          glGetString (GL_RENDERER),
634          glGetString (GL_VERSION),
635          egl_major, egl_minor);
636   }
637
638   if (rh->jwxyz_gl_p) {
639     const GLubyte *extensions = glGetString (GL_EXTENSIONS);
640     rh->gl_fbo_p = jwzgles_gluCheckExtension (
641       (const GLubyte *)"GL_OES_framebuffer_object", extensions);
642
643     if (rh->gl_fbo_p) {
644       glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &rh->fb_default);
645       Assert (!rh->fb_default, "default framebuffer not current framebuffer");
646       glGenFramebuffersOES (1, &rh->fb_pixmap);
647       wnd->texture = 0;
648     } else {
649       wnd->egl_surface = rh->egl_surface;
650     }
651
652     rh->frontbuffer_p = False;
653
654     if (rh->xsft->visual == DEFAULT_VISUAL ||
655         (rh->xsft->visual == GL_VISUAL &&
656          strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
657
658       rh->frontbuffer_p = True;
659
660 # if 0 /* Might need to be 0 for Adreno...? */
661       if (egl_major > 1 || (egl_major == 1 && egl_minor >= 2)) {
662         EGLint surface_type;
663         eglGetConfigAttrib(rh->egl_display, rh->egl_config, EGL_SURFACE_TYPE,
664                            &surface_type);
665         if(surface_type & EGL_SWAP_BEHAVIOR_PRESERVED_BIT) {
666           eglSurfaceAttrib(rh->egl_display, rh->egl_surface, EGL_SWAP_BEHAVIOR,
667                            EGL_BUFFER_PRESERVED);
668           rh->frontbuffer_p = False;
669         }
670       }
671 # endif
672
673       if (rh->frontbuffer_p) {
674         /* create_pixmap needs rh->gl_fbo_p and wnd->frame. */
675         create_pixmap (wnd, wnd);
676
677         /* No preserving backbuffers, so manual blit from back to "front". */
678         rh->frontbuffer.type = PIXMAP;
679         rh->frontbuffer.frame = wnd->frame;
680         rh->frontbuffer.pixmap.depth = visual_depth (NULL, NULL);
681
682         if(rh->gl_fbo_p) {
683           rh->frontbuffer.texture = 0;
684         } else {
685           Assert (wnd->egl_surface != rh->egl_surface,
686                   "oops: wnd->egl_surface == rh->egl_surface");
687           rh->frontbuffer.egl_surface = rh->egl_surface;
688         }
689       }
690     }
691
692     rh->dpy = jwxyz_gl_make_display(wnd);
693
694   } else {
695
696     create_pixmap (wnd, wnd);
697
698     static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
699     rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
700
701   }
702
703   Assert(wnd == XRootWindow(rh->dpy, 0), "Wrong root window.");
704   // TODO: Zero looks right, but double-check that is the right number
705
706   /* Requires valid rh->dpy. */
707   if (rh->jwxyz_gl_p)
708     rh->copy_gc = XCreateGC (rh->dpy, &rh->frontbuffer, 0, NULL);
709
710   if (rh->xsft->visual == GL_VISUAL)
711     rh->gles_state = jwzgles_make_state();
712  END: ;
713 }
714
715
716 #undef DEBUG_FPS
717
718 #ifdef DEBUG_FPS
719
720 static double
721 double_time (void)
722 {
723   struct timeval now;
724 # ifdef GETTIMEOFDAY_TWO_ARGS
725   struct timezone tzp;
726   gettimeofday(&now, &tzp);
727 # else
728   gettimeofday(&now);
729 # endif
730
731   return (now.tv_sec + ((double) now.tv_usec * 0.000001));
732 }
733
734 #endif
735
736 // Animates a single frame of the current hack.
737 //
738 static jlong
739 drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
740 {
741 # ifdef DEBUG_FPS
742   double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
743   fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
744 # endif
745
746   unsigned long delay = 0;
747
748   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
749
750   Window wnd = rh->window;
751
752   prepare_context (rh);
753
754   if (rh->egl_p) {
755     /* There is some kind of weird redisplay race condition between Settings
756        and the launching hack: e.g., Abstractile does XClearWindow at init,
757        but the screen is getting filled with random bits.  So let's wait a
758        few frames before really starting up.
759
760        TODO: Is this still true?
761      */
762     if (++rh->frame_count < 8) {
763       /* glClearColor (1.0, 0.0, 1.0, 0.0); */
764       glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */
765       goto END;
766     }
767   }
768
769 # ifdef DEBUG_FPS
770   fps1 = double_time();
771 # endif
772
773   // The init function might do graphics (e.g. XClearWindow) so it has
774   // to be run from inside onDrawFrame, not onSurfaceChanged.
775
776   if (! rh->initted_p) {
777
778     void *(*init_cb) (Display *, Window, void *) =
779       (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
780
781     if (rh->xsft->visual == DEFAULT_VISUAL) {
782       unsigned int bg =
783         get_pixel_resource (rh->dpy, 0, "background", "Background");
784       XSetWindowBackground (rh->dpy, wnd, bg);
785       XClearWindow (rh->dpy, wnd);
786     }
787
788     rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg);
789     rh->initted_p = True;
790
791     /* ignore_rotation_p doesn't quite work at the moment. */
792     rh->ignore_rotation_p = False;
793 /*
794       (rh->xsft->visual == DEFAULT_VISUAL &&
795        get_boolean_resource (rh->dpy, "ignoreRotation", "IgnoreRotation"));
796 */
797
798     if (get_boolean_resource (rh->dpy, "doFPS", "DoFPS")) {
799       rh->fpst = fps_init (rh->dpy, wnd);
800       if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
801     } else {
802       rh->fpst = NULL;
803     }
804
805     if ((*env)->ExceptionOccurred(env)) abort();
806   }
807
808 # ifdef DEBUG_FPS
809   fps2 = double_time();
810 # endif
811
812   // Apparently events don't come in on the drawing thread, and JNI flips
813   // out.  So we queue them there and run them here.
814   // TODO: Events should be coming in on the drawing thread now, so dump this.
815   send_queued_events (rh);
816
817 # ifdef DEBUG_FPS
818   fps3 = double_time();
819 # endif
820
821   delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
822
823   if (rh->jwxyz_gl_p)
824     jwxyz_gl_flush (rh->dpy);
825
826 # ifdef DEBUG_FPS
827   fps4 = double_time();
828 # endif
829   if (rh->fpst && rh->xsft->fps_cb)
830     rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure);
831
832   if (rh->egl_p) {
833     if (rh->jwxyz_gl_p && rh->frontbuffer_p) {
834       jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc,
835                           0, 0, wnd->frame.width, wnd->frame.height,
836                           0, 0);
837     }
838
839     // Getting failure here before/during/after resize, sometimes. Log sez:
840     // W/Adreno-EGLSUB(18428): <DequeueBuffer:607>: dequeue native buffer fail: No such device, buffer=0x5f93bf5c, handle=0x0
841     if (!eglSwapBuffers(rh->egl_display, rh->egl_surface)) {
842       Log ("eglSwapBuffers failed: 0x%x (probably asynchronous resize)",
843            eglGetError());
844     }
845   } else {
846     ANativeWindow_Buffer buffer;
847     ARect rect = {0, 0, wnd->frame.width, wnd->frame.height};
848     int32_t result = ANativeWindow_lock(rh->native_window, &buffer, &rect);
849     if (result) {
850       Log ("ANativeWindow_lock failed (result = %d), frame dropped", result);
851     } else {
852       /* Android can resize surfaces asynchronously. */
853       if (wnd->frame.width != buffer.width ||
854           wnd->frame.height != buffer.height) {
855         Log ("buffer/window size mismatch: %dx%d (format = %d), wnd: %dx%d",
856              buffer.width, buffer.height, buffer.format,
857              wnd->frame.width, wnd->frame.height);
858       }
859
860       Assert (buffer.format == WINDOW_FORMAT_RGBA_8888 ||
861               buffer.format == WINDOW_FORMAT_RGBX_8888,
862               "bad buffer format");
863
864       jwxyz_blit (wnd->image_data, jwxyz_image_pitch (wnd), 0, 0,
865                   buffer.bits, buffer.stride * 4, 0, 0,
866                   MIN(wnd->frame.width, buffer.width),
867                   MIN(wnd->frame.height, buffer.height));
868       // TODO: Clear any area to sides and bottom.
869
870       ANativeWindow_unlockAndPost (rh->native_window);
871     }
872   }
873
874  END: ;
875
876 # ifdef DEBUG_FPS
877   Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
878       (int) ((fps1-fps0)*1000000),
879       (int) ((fps2-fps1)*1000000),
880       (int) ((fps3-fps2)*1000000),
881       (int) ((fps4-fps3)*1000000),
882       (int) ((double_time()-fps4)*1000000));
883 # endif
884
885   return delay;
886 }
887
888
889 // Extracts the C structure that is stored in the jwxyz Java object.
890 static struct running_hack *
891 getRunningHack (JNIEnv *env, jobject thiz)
892 {
893   jlong result = (*env)->GetLongField (env, thiz, runningHackField);
894   struct running_hack *rh = (struct running_hack *)(intptr_t)result;
895   if (rh)
896     rh->jobject = thiz;  // update this every time we call into C
897   return rh;
898 }
899
900 // Look up a class and mark it global in the provided variable.
901 static jclass
902 acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
903 {
904   jclass clazz = (*env)->FindClass(env, className);
905   *globalRef = (*env)->NewGlobalRef(env, clazz);
906   return clazz;
907 }
908
909
910 /* Note: to find signature strings for native methods:
911    cd ./project/xscreensaver/build/intermediates/classes/debug/
912    javap -s -p org.jwz.xscreensaver.jwxyz
913  */
914
915
916 // Implementation of jwxyz's nativeInit Java method.
917 //
918 JNIEXPORT void JNICALL
919 Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
920                                             jstring jhack, jobject defaults,
921                                             jint w, jint h,
922                                             jobject jni_surface)
923 {
924   pthread_mutex_lock(&mutg);
925
926   struct running_hack *rh = calloc(1, sizeof(struct running_hack));
927
928   if ((*env)->ExceptionOccurred(env)) abort();
929
930   // #### simplify
931   if (!classRefCount) {
932     jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
933     globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
934     runningHackField = (*env)->GetFieldID
935       (env, classjwxyz, "nativeRunningHackPtr", "J");
936     if ((*env)->ExceptionOccurred(env)) abort();
937
938     jclass classIterable =
939       acquireClass(env, "java/lang/Iterable", &globalRefIterable);
940     iterableIterator = (*env)->GetMethodID
941       (env, classIterable, "iterator", "()Ljava/util/Iterator;");
942     if ((*env)->ExceptionOccurred(env)) abort();
943
944     jclass classIterator =
945       acquireClass(env, "java/util/Iterator", &globalRefIterator);
946     iteratorHasNext = (*env)->GetMethodID
947       (env, classIterator, "hasNext", "()Z");
948     iteratorNext = (*env)->GetMethodID
949       (env, classIterator, "next", "()Ljava/lang/Object;");
950     if ((*env)->ExceptionOccurred(env)) abort();
951
952     jclass classMapEntry =
953       acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
954     entryGetKey = (*env)->GetMethodID
955       (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
956     entryGetValue = (*env)->GetMethodID
957       (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
958     if ((*env)->ExceptionOccurred(env)) abort();
959   }
960
961   ++classRefCount;
962
963   // Store the C struct into the Java object.
964   (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
965
966   // TODO: Sort the list so binary search works.
967   const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
968
969   int chosen = 0;
970   for (;;) {
971     if (chosen == countof(function_table)) {
972       Log ("Hack not found: %s", hack);
973       abort();
974     }
975     if (!strcmp(function_table[chosen].progname, hack))
976       break;
977     chosen++;
978   }
979
980   (*env)->ReleaseStringUTFChars(env, jhack, hack);
981
982   doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
983           jni_surface);
984
985   pthread_mutex_unlock(&mutg);
986 }
987
988
989 JNIEXPORT void JNICALL
990 Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
991                                               jint w, jint h, jdouble rot)
992 {
993   pthread_mutex_lock(&mutg);
994   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
995
996   current_rotation = rot;
997
998   Log ("native rotation: %f", current_rotation);
999
1000   struct running_hack *rh = getRunningHack(env, thiz);
1001
1002   prepare_context (rh);
1003
1004   if (rh->egl_p) {
1005     glViewport (0, 0, w, h);
1006   } else {
1007     int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h,
1008                                                    WINDOW_FORMAT_RGBX_8888);
1009     if (result < 0)
1010       Log ("failed to resize surface (%d)", result);
1011   }
1012
1013   Window wnd = rh->window;
1014   wnd->frame.x = 0;
1015   wnd->frame.y = 0;
1016   wnd->frame.width  = w;
1017   wnd->frame.height = h;
1018
1019   if (ignore_rotation_p(rh->dpy) &&
1020       rot != 0 && rot != 180 && rot != -180) {
1021     int swap = w;
1022     w = h;
1023     h = swap;
1024     wnd->frame.width  = w;
1025     wnd->frame.height = h;
1026   }
1027
1028   if (rh->jwxyz_gl_p) {
1029     if (rh->frontbuffer_p) {
1030       free_pixmap (rh, wnd);
1031       create_pixmap (wnd, wnd);
1032
1033       rh->frontbuffer.frame = wnd->frame;
1034       if (!rh->gl_fbo_p)
1035         rh->frontbuffer.egl_surface = rh->egl_surface;
1036     }
1037
1038     jwxyz_window_resized (rh->dpy);
1039   } else {
1040     free_pixmap (rh, wnd);
1041     create_pixmap (wnd, wnd);
1042     XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
1043   }
1044
1045   if (rh->initted_p)
1046     rh->xsft->reshape_cb (rh->dpy, rh->window, rh->closure,
1047                           wnd->frame.width, wnd->frame.height);
1048
1049   if (rh->xsft->visual == GL_VISUAL) {
1050     glMatrixMode (GL_PROJECTION);
1051     glRotatef (-rot, 0, 0, 1);
1052     glMatrixMode (GL_MODELVIEW);
1053   }
1054
1055  END:
1056   pthread_mutex_unlock(&mutg);
1057 }
1058
1059
1060 JNIEXPORT jlong JNICALL
1061 Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
1062 {
1063   pthread_mutex_lock(&mutg);
1064   struct running_hack *rh = getRunningHack(env, thiz);
1065   jlong result = drawXScreenSaver(env, rh);
1066   pthread_mutex_unlock(&mutg);
1067   return result;
1068 }
1069
1070
1071 // TODO: Check Java side is calling this properly
1072 JNIEXPORT void JNICALL
1073 Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
1074 {
1075   pthread_mutex_lock(&mutg);
1076   if (setjmp (jmp_target)) goto END;  // Jump here from jwxyz_abort and return.
1077
1078   struct running_hack *rh = getRunningHack(env, thiz);
1079
1080   prepare_context (rh);
1081
1082   if (rh->fpst)
1083     rh->xsft->fps_free (rh->fpst);
1084   if (rh->initted_p)
1085     rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
1086   if (rh->jwxyz_gl_p)
1087     XFreeGC (rh->dpy, rh->copy_gc);
1088   if (rh->xsft->visual == GL_VISUAL)
1089     jwzgles_free_state ();
1090
1091   if (rh->jwxyz_gl_p)
1092     jwxyz_gl_free_display(rh->dpy);
1093   else
1094     jwxyz_image_free_display(rh->dpy);
1095
1096   if (rh->egl_p) {
1097     // eglDestroy* probably isn't necessary here.
1098     eglMakeCurrent (rh->egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE,
1099                     EGL_NO_CONTEXT);
1100     eglDestroySurface (rh->egl_display, rh->egl_surface);
1101     eglDestroyContext (rh->egl_display, rh->egl_ctx);
1102     eglTerminate (rh->egl_display);
1103   } else {
1104     free_pixmap (rh, rh->window);
1105     if (rh->native_window)
1106       ANativeWindow_release (rh->native_window);
1107   }
1108
1109   free(rh);
1110   (*env)->SetLongField(env, thiz, runningHackField, 0);
1111
1112   --classRefCount;
1113   if (!classRefCount) {
1114     (*env)->DeleteGlobalRef(env, globalRefjwxyz);
1115     (*env)->DeleteGlobalRef(env, globalRefIterable);
1116     (*env)->DeleteGlobalRef(env, globalRefIterator);
1117     (*env)->DeleteGlobalRef(env, globalRefMapEntry);
1118   }
1119
1120  END:
1121   pthread_mutex_unlock(&mutg);
1122 }
1123
1124
1125 static int
1126 send_event (struct running_hack *rh, XEvent *e)
1127 {
1128   // Assumes mutex is locked and context is prepared
1129
1130   int *xP = 0, *yP = 0;
1131   switch (e->xany.type) {
1132   case ButtonPress: case ButtonRelease:
1133     xP = &e->xbutton.x;
1134     yP = &e->xbutton.y;
1135     break;
1136   case MotionNotify:
1137     xP = &e->xmotion.x;
1138     yP = &e->xmotion.y;
1139     break;
1140   }
1141
1142   // Rotate the coordinates in the events to match the pixels.
1143   if (xP) {
1144     if (ignore_rotation_p (rh->dpy)) {
1145       Window win = XRootWindow (rh->dpy, 0);
1146       int w = win->frame.width;
1147       int h = win->frame.height;
1148       int swap;
1149       switch ((int) current_rotation) {
1150       case 180: case -180:                              // #### untested
1151         *xP = w - *xP;
1152         *yP = h - *yP;
1153         break;
1154       case 90: case -270:
1155         swap = *xP; *xP = *yP; *yP = swap;
1156         *yP = h - *yP;
1157         break;
1158       case -90: case 270:                               // #### untested
1159         swap = *xP; *xP = *yP; *yP = swap;
1160         *xP = w - *xP;
1161         break;
1162       }
1163     }
1164
1165     rh->window->window.last_mouse_x = *xP;
1166     rh->window->window.last_mouse_y = *yP;
1167   }
1168
1169   return (rh->xsft->event_cb
1170           ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
1171           : 0);
1172 }
1173
1174
1175 static void
1176 send_queued_events (struct running_hack *rh)
1177 {
1178   struct event_queue *event, *next;
1179   if (! rh->event_queue) return;
1180   for (event = rh->event_queue, next = event->next;
1181        event;
1182        event = next, next = (event ? event->next : 0)) {
1183     if (! send_event (rh, &event->event)) {
1184       // #### flash the screen or something
1185     }
1186     free (event);
1187   }
1188   rh->event_queue = 0;
1189 }
1190
1191
1192 static void
1193 queue_event (JNIEnv *env, jobject thiz, XEvent *e)
1194 {
1195   pthread_mutex_lock (&mutg);
1196   struct running_hack *rh = getRunningHack (env, thiz);
1197   struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
1198   memcpy (&q->event, e, sizeof(*e));
1199   q->next = 0;
1200
1201   // Put it at the end.
1202   struct event_queue *oq;
1203   for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
1204     ;
1205   if (oq)
1206     oq->next = q;
1207   else
1208     rh->event_queue = q;
1209
1210   pthread_mutex_unlock (&mutg);
1211 }
1212
1213
1214 JNIEXPORT void JNICALL
1215 Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
1216                                                  int x, int y, jboolean down)
1217 {
1218   XEvent e;
1219   memset (&e, 0, sizeof(e));
1220   e.xany.type = (down ? ButtonPress : ButtonRelease);
1221   e.xbutton.button = Button1;
1222   e.xbutton.x = x;
1223   e.xbutton.y = y;
1224   queue_event (env, thiz, &e);
1225 }
1226
1227 JNIEXPORT void JNICALL
1228 Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
1229                                                  int x, int y)
1230 {
1231   XEvent e;
1232   memset (&e, 0, sizeof(e));
1233   e.xany.type = MotionNotify;
1234   e.xmotion.x = x;
1235   e.xmotion.y = y;
1236   queue_event (env, thiz, &e);
1237 }
1238
1239 JNIEXPORT void JNICALL
1240 Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
1241                                               jboolean down_p, 
1242                                               int code, int mods)
1243 {
1244   XEvent e;
1245   memset (&e, 0, sizeof(e));
1246   e.xkey.keycode = code;
1247   e.xkey.state = code;
1248   e.xany.type = (down_p ? KeyPress : KeyRelease);
1249   queue_event (env, thiz, &e);
1250   e.xany.type = KeyRelease;
1251   queue_event (env, thiz, &e);
1252 }
1253
1254
1255 /***************************************************************************
1256   Backend functions for jwxyz-gl.c
1257  */
1258
1259 static void
1260 finish_bind_drawable (Display *dpy, Drawable dst)
1261 {
1262   jwxyz_assert_gl ();
1263
1264   glViewport (0, 0, dst->frame.width, dst->frame.height);
1265   jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
1266 }
1267
1268
1269 static void
1270 bind_drawable_fbo (struct running_hack *rh, Drawable d)
1271 {
1272   glBindFramebufferOES (GL_FRAMEBUFFER_OES,
1273                         d->texture ? rh->fb_pixmap : rh->fb_default);
1274   if (d->texture) {
1275     glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
1276                                GL_TEXTURE_2D, d->texture, 0);
1277   }
1278 }
1279
1280
1281 void
1282 jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
1283 {
1284   struct running_hack *rh = w->window.rh;
1285   JNIEnv *env = w->window.rh->jni_env;
1286   if ((*env)->ExceptionOccurred(env)) abort();
1287   if (rh->current_drawable != d) {
1288     if (rh->gl_fbo_p) {
1289       bind_drawable_fbo (rh, d);
1290     } else {
1291       eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
1292     }
1293     finish_bind_drawable (dpy, d);
1294     rh->current_drawable = d;
1295   }
1296 }
1297
1298 void
1299 jwxyz_gl_copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
1300                     int src_x, int src_y,
1301                     unsigned int width, unsigned int height,
1302                     int dst_x, int dst_y)
1303 {
1304   Window w = XRootWindow (dpy, 0);
1305   struct running_hack *rh = w->window.rh;
1306
1307   jwxyz_gl_flush (dpy);
1308
1309   if (rh->gl_fbo_p && src->texture && src != dst) {
1310     bind_drawable_fbo (rh, dst);
1311     finish_bind_drawable (dpy, dst);
1312     rh->current_drawable = NULL;
1313
1314     jwxyz_gl_set_gc (dpy, gc);
1315
1316     glBindTexture (GL_TEXTURE_2D, src->texture);
1317
1318     jwxyz_gl_draw_image (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width),
1319                          to_pow2(src->frame.height),
1320                          src_x, src->frame.height - src_y - height,
1321                          jwxyz_drawable_depth (src), width, height,
1322                          dst_x, dst_y, False);
1323     return;
1324   }
1325
1326 #if 1
1327   // Kumppa: 0.24 FPS
1328   // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
1329   // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
1330
1331   rh->current_drawable = NULL;
1332   if (rh->gl_fbo_p)
1333     bind_drawable_fbo (rh, src);
1334   else
1335     eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
1336
1337   jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
1338                                      width, height, dst_x, dst_y);
1339
1340   if (rh->gl_fbo_p)
1341     bind_drawable_fbo (rh, dst);
1342   finish_bind_drawable (dpy, dst);
1343
1344   jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
1345                                       jwxyz_drawable_depth (src),
1346                                       width, height, dst_x, dst_y);
1347
1348 #else
1349   // Kumppa: 0.17 FPS
1350   jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
1351                                   width, height, dst_x, dst_y);
1352 #endif
1353   jwxyz_assert_gl ();
1354 }
1355
1356
1357 void
1358 jwxyz_assert_drawable (Window main_window, Drawable d)
1359 {
1360   check_gl_error("jwxyz_assert_drawable");
1361 }
1362
1363
1364 void
1365 jwxyz_assert_gl (void)
1366 {
1367   check_gl_error("jwxyz_assert_gl");
1368 }
1369
1370
1371 /***************************************************************************
1372   Backend functions for jwxyz-image.c
1373  */
1374
1375 ptrdiff_t
1376 jwxyz_image_pitch (Drawable d)
1377 {
1378   return d->frame.width * 4;
1379 }
1380
1381 void *
1382 jwxyz_image_data (Drawable d)
1383 {
1384   Assert (d->image_data, "no image storage (i.e. keep Xlib off the Window)");
1385   return d->image_data;
1386 }
1387
1388
1389 const XRectangle *
1390 jwxyz_frame (Drawable d)
1391 {
1392   return &d->frame;
1393 }
1394
1395
1396 unsigned int
1397 jwxyz_drawable_depth (Drawable d)
1398 {
1399   return (d->type == WINDOW
1400           ? visual_depth (NULL, NULL)
1401           : d->pixmap.depth);
1402 }
1403
1404
1405 void
1406 jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
1407 {
1408   xvpos->x = 0;
1409   xvpos->y = 0;
1410
1411   if (xp) {
1412     xp->x = w->window.last_mouse_x;
1413     xp->y = w->window.last_mouse_y;
1414   }
1415 }
1416
1417
1418 static void
1419 screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
1420 {
1421   fps_compute (fpst, 0, -1);
1422   fps_draw (fpst);
1423 }
1424
1425
1426 Pixmap
1427 XCreatePixmap (Display *dpy, Drawable d,
1428                unsigned int width, unsigned int height, unsigned int depth)
1429 {
1430   Window win = XRootWindow(dpy, 0);
1431
1432   Pixmap p = malloc(sizeof(*p));
1433   p->type = PIXMAP;
1434   p->frame.x = 0;
1435   p->frame.y = 0;
1436   p->frame.width = width;
1437   p->frame.height = height;
1438
1439   Assert(depth == 1 || depth == visual_depth(NULL, NULL),
1440          "XCreatePixmap: bad depth");
1441   p->pixmap.depth = depth;
1442
1443   create_pixmap (win, p);
1444
1445   /* For debugging. */
1446 # if 0
1447   jwxyz_bind_drawable (dpy, win, p);
1448   glClearColor (frand(1), frand(1), frand(1), 0);
1449   glClear (GL_COLOR_BUFFER_BIT);
1450 # endif
1451
1452   return p;
1453 }
1454
1455
1456 int
1457 XFreePixmap (Display *d, Pixmap p)
1458 {
1459   struct running_hack *rh = XRootWindow(d, 0)->window.rh;
1460
1461   if (rh->jwxyz_gl_p) {
1462     jwxyz_gl_flush (d);
1463
1464     if (rh->current_drawable == p)
1465       rh->current_drawable = NULL;
1466   }
1467
1468   free_pixmap (rh, p);
1469   free (p);
1470   return 0;
1471 }
1472
1473
1474 double
1475 current_device_rotation (void)
1476 {
1477   return current_rotation;
1478 }
1479
1480 Bool
1481 ignore_rotation_p (Display *dpy)
1482 {
1483   struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
1484   return rh->ignore_rotation_p;
1485 }
1486
1487
1488 static char *
1489 jstring_dup (JNIEnv *env, jstring str)
1490 {
1491   Assert (str, "expected jstring, not null");
1492   const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
1493   size_t len = (*env)->GetStringUTFLength (env, str) + 1;
1494   char *result = malloc (len);
1495   if (result) {
1496     memcpy (result, cstr, len);
1497   }
1498   (*env)->ReleaseStringUTFChars (env, str, cstr);
1499   return result;
1500 }
1501
1502
1503 static char *
1504 get_string_resource_window (Window window, char *name)
1505 {
1506   JNIEnv *env = window->window.rh->jni_env;
1507   jobject obj = window->window.rh->jobject;
1508
1509   if ((*env)->ExceptionOccurred(env)) abort();
1510   jstring jstr = (*env)->NewStringUTF (env, name);
1511   jclass     c = (*env)->GetObjectClass (env, obj);
1512   jmethodID  m = (*env)->GetMethodID (env, c, "getStringResource",
1513                            "(Ljava/lang/String;)Ljava/lang/String;");
1514   if ((*env)->ExceptionOccurred(env)) abort();
1515
1516   jstring jvalue = (m
1517                   ? (*env)->CallObjectMethod (env, obj, m, jstr)
1518                   : NULL);
1519   (*env)->DeleteLocalRef (env, c);
1520   (*env)->DeleteLocalRef (env, jstr);
1521   char *ret = 0;
1522   if (jvalue)
1523     ret = jstring_dup (env, jvalue);
1524
1525   Log("pref %s = %s", name, (ret ? ret : "(null)"));
1526   return ret;
1527 }
1528
1529
1530 char *
1531 get_string_resource (Display *dpy, char *name, char *class)
1532 {
1533   return get_string_resource_window (RootWindow (dpy, 0), name);
1534 }
1535
1536
1537 /* Returns the contents of the URL. */
1538 char *
1539 textclient_mobile_url_string (Display *dpy, const char *url)
1540 {
1541   Window window = RootWindow (dpy, 0);
1542   JNIEnv *env = window->window.rh->jni_env;
1543   jobject obj = window->window.rh->jobject;
1544
1545   jstring jstr  = (*env)->NewStringUTF (env, url);
1546   jclass      c = (*env)->GetObjectClass (env, obj);
1547   jmethodID   m = (*env)->GetMethodID (env, c, "loadURL",
1548                             "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
1549   if ((*env)->ExceptionOccurred(env)) abort();
1550   jobject buf = (m
1551                  ? (*env)->CallObjectMethod (env, obj, m, jstr)
1552                  : NULL);
1553   (*env)->DeleteLocalRef (env, c);
1554   (*env)->DeleteLocalRef (env, jstr);
1555
1556   char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1557   char *body2;
1558   if (body) {
1559     int L = (*env)->GetDirectBufferCapacity (env, buf);
1560     body2 = malloc (L + 1);
1561     memcpy (body2, body, L);
1562     body2[L] = 0;
1563   } else {
1564     body2 = strdup ("ERROR");
1565   }
1566
1567   if (buf)
1568     (*env)->DeleteLocalRef (env, buf);
1569
1570   return body2;
1571 }
1572
1573
1574 float
1575 jwxyz_scale (Window main_window)
1576 {
1577   // TODO: Use the actual device resolution.
1578   return 2;
1579 }
1580
1581 float
1582 jwxyz_font_scale (Window main_window)
1583 {
1584   return jwxyz_scale (main_window);
1585 }
1586
1587
1588 const char *
1589 jwxyz_default_font_family (int require)
1590 {
1591   /* Font families in XLFDs are totally ignored (for now). */
1592   return "sans-serif";
1593 }
1594
1595
1596 void *
1597 jwxyz_load_native_font (Window window,
1598                         int traits_jwxyz, int mask_jwxyz,
1599                         const char *font_name_ptr, size_t font_name_length,
1600                         int font_name_type, float size,
1601                         char **family_name_ret,
1602                         int *ascent_ret, int *descent_ret)
1603 {
1604   JNIEnv *env = window->window.rh->jni_env;
1605   jobject obj = window->window.rh->jobject;
1606
1607   jstring jname = NULL;
1608   if (font_name_ptr) {
1609     char *name_nul = malloc(font_name_length + 1);
1610     memcpy(name_nul, font_name_ptr, font_name_length);
1611     name_nul[font_name_length] = 0;
1612     jname = (*env)->NewStringUTF (env, name_nul);
1613     free(name_nul);
1614   }
1615
1616   jclass     c = (*env)->GetObjectClass (env, obj);
1617   jmethodID  m = (*env)->GetMethodID (env, c, "loadFont",
1618                            "(IILjava/lang/String;IF)[Ljava/lang/Object;");
1619   if ((*env)->ExceptionOccurred(env)) abort();
1620
1621   jobjectArray array = (m
1622                         ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
1623                                                     (jint)traits_jwxyz, jname,
1624                                                     (jint)font_name_type, (jfloat)size)
1625                         : NULL);
1626
1627   (*env)->DeleteLocalRef (env, c);
1628
1629   if (array) {
1630     jobject font = (*env)->GetObjectArrayElement (env, array, 0);
1631     jobject family_name =
1632       (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
1633     jobject asc  = (*env)->GetObjectArrayElement (env, array, 2);
1634     jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
1635     if ((*env)->ExceptionOccurred(env)) abort();
1636
1637     if (family_name_ret)
1638       *family_name_ret = jstring_dup (env, family_name);
1639
1640     jobject paint = (*env)->NewGlobalRef (env, font);
1641     if ((*env)->ExceptionOccurred(env)) abort();
1642
1643     c = (*env)->GetObjectClass(env, asc);
1644     m = (*env)->GetMethodID (env, c, "floatValue", "()F");
1645     if ((*env)->ExceptionOccurred(env)) abort();
1646
1647     *ascent_ret  = (int) (*env)->CallFloatMethod (env, asc,  m);
1648     *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
1649
1650     return (void *) paint;
1651   } else {
1652     return 0;
1653   }
1654 }
1655
1656
1657 void
1658 jwxyz_release_native_font (Display *dpy, void *native_font)
1659 {
1660   Window window = RootWindow (dpy, 0);
1661   JNIEnv *env = window->window.rh->jni_env;
1662   if ((*env)->ExceptionOccurred(env)) abort();
1663   (*env)->DeleteGlobalRef (env, (jobject) native_font);
1664   if ((*env)->ExceptionOccurred(env)) abort();
1665 }
1666
1667
1668 /* If the local reference table fills up, use this to figure out where
1669    you missed a call to DeleteLocalRef. */
1670 /*
1671 static void dump_reference_tables(JNIEnv *env)
1672 {
1673   jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
1674   jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
1675                                            "()V");
1676   (*env)->CallStaticVoidMethod (env, c, m);
1677   (*env)->DeleteLocalRef (env, c);
1678 }
1679 */
1680
1681
1682 // Returns the metrics of the multi-character, single-line UTF8 or Latin1
1683 // string.  If pixmap_ret is provided, also renders the text.
1684 //
1685 void
1686 jwxyz_render_text (Display *dpy, void *native_font,
1687                    const char *str, size_t len, Bool utf8, Bool antialias_p,
1688                    XCharStruct *cs, char **pixmap_ret)
1689 {
1690   Window window = RootWindow (dpy, 0);
1691   JNIEnv *env = window->window.rh->jni_env;
1692   jobject obj = window->window.rh->jobject;
1693
1694   char *s2;
1695
1696   if (utf8) {
1697     s2 = malloc (len + 1);
1698     memcpy (s2, str, len);
1699     s2[len] = 0;
1700   } else {      // Convert Latin1 to UTF8
1701     s2 = malloc (len * 2 + 1);
1702     unsigned char *s3 = (unsigned char *) s2;
1703     int i;
1704     for (i = 0; i < len; i++) {
1705       unsigned char c = ((unsigned char *) str)[i];
1706       if (! (c & 0x80)) {
1707         *s3++ = c;
1708       } else {
1709         *s3++ = (0xC0 | (0x03 & (c >> 6)));
1710         *s3++ = (0x80 | (0x3F & c));
1711       }
1712     }
1713     *s3 = 0;
1714   }
1715
1716   jstring jstr  = (*env)->NewStringUTF (env, s2);
1717   jclass      c = (*env)->GetObjectClass (env, obj);
1718   jmethodID   m = (*env)->GetMethodID (env, c, "renderText",
1719     "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;");
1720   if ((*env)->ExceptionOccurred(env)) abort();
1721   jobject buf =
1722     (m
1723      ? (*env)->CallObjectMethod (env, obj, m,
1724                                  (jobject) native_font,
1725                                  jstr,
1726                                  (pixmap_ret ? JNI_TRUE : JNI_FALSE),
1727                                  antialias_p)
1728      : NULL);
1729   (*env)->DeleteLocalRef (env, c);
1730   (*env)->DeleteLocalRef (env, jstr);
1731   free (s2);
1732
1733   if ((*env)->ExceptionOccurred(env)) abort();
1734   unsigned char *bits = (unsigned char *)
1735     (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
1736   if (bits) {
1737     int i = 0;
1738     int L = (*env)->GetDirectBufferCapacity (env, buf);
1739     if (L < 10) abort();
1740     cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1741     cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1742     cs->width    = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1743     cs->ascent   = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1744     cs->descent  = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
1745
1746     if (pixmap_ret) {
1747       char *pix = malloc (L - i);
1748       if (! pix) abort();
1749       memcpy (pix, bits + i, L - i);
1750       *pixmap_ret = pix;
1751     }
1752   } else {
1753     memset (cs, 0, sizeof(*cs));
1754     if (pixmap_ret)
1755       *pixmap_ret = 0;
1756   }
1757
1758   if (buf)
1759     (*env)->DeleteLocalRef (env, buf);
1760 }
1761
1762
1763 // Returns the verbose Unicode name of this character, like "agrave" or
1764 // "daggerdouble".  Used by Unicrud, and by Fontglide with debugMetrics.
1765 //
1766 char *
1767 jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
1768 {
1769   JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
1770   /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
1771   jclass
1772     c = (*env)->FindClass (env, "java/lang/Character"),
1773     c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
1774
1775   if ((*env)->ExceptionOccurred(env)) abort();
1776   jmethodID m = (*env)->GetStaticMethodID (
1777     env, c, "getName", "(I)Ljava/lang/String;");
1778   jthrowable exc = (*env)->ExceptionOccurred(env);
1779   if (exc) {
1780     if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
1781       (*env)->ExceptionClear (env);
1782       Assert (!m, "jwxyz_unicode_character_name: m?");
1783     } else {
1784       abort();
1785     }
1786   }
1787
1788   char *ret = NULL;
1789
1790   if (m) {
1791     jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
1792     if (name)
1793      ret = jstring_dup (env, name);
1794   }
1795
1796   if (!ret) {
1797     asprintf(&ret, "U+%.4lX", uc);
1798   }
1799
1800   return ret;
1801 }
1802
1803
1804 /* Called from utils/grabclient.c */
1805 char *
1806 jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc)
1807 {
1808   Window window = RootWindow (dpy, 0);
1809   struct running_hack *rh = window->window.rh;
1810   JNIEnv *env = rh->jni_env;
1811   jobject obj = rh->jobject;
1812
1813   Bool images_p =
1814     get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
1815   Bool grab_p =
1816     get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
1817   Bool rotate_p =
1818     get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
1819
1820   if (!images_p && !grab_p)
1821     return 0;
1822
1823   if (grab_p && images_p) {
1824     grab_p = !(random() & 5);    /* if both, screenshot 1/5th of the time */
1825     images_p = !grab_p;
1826   }
1827
1828   jclass      c = (*env)->GetObjectClass (env, obj);
1829   jmethodID   m = (*env)->GetMethodID (env, c, 
1830                                        (grab_p
1831                                         ? "getScreenshot"
1832                                         : "checkThenLoadRandomImage"),
1833                                        "(IIZ)[Ljava/lang/Object;");
1834   if ((*env)->ExceptionOccurred(env)) abort();
1835   jobjectArray img_name = (
1836     m
1837     ? (*env)->CallObjectMethod (env, obj, m,
1838                                 drawable->frame.width, drawable->frame.height,
1839                                 (rotate_p ? JNI_TRUE : JNI_FALSE))
1840     : NULL);
1841   if ((*env)->ExceptionOccurred(env)) abort();
1842   (*env)->DeleteLocalRef (env, c);
1843
1844   if (!img_name) {
1845     fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
1846     return NULL;
1847   }
1848
1849   jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0);
1850
1851   AndroidBitmapInfo bmp_info;
1852   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1853
1854   XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL),
1855                               ZPixmap, 0, NULL,
1856                               bmp_info.width, bmp_info.height, 0,
1857                               bmp_info.stride);
1858
1859   AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data);
1860
1861   XPutImage (dpy, drawable, gc, img, 0, 0,
1862              (drawable->frame.width  - bmp_info.width) / 2,
1863              (drawable->frame.height - bmp_info.height) / 2,
1864              bmp_info.width, bmp_info.height);
1865
1866   AndroidBitmap_unlockPixels (env, jbitmap);
1867   img->data = NULL;
1868   XDestroyImage (img);
1869
1870   return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1));
1871 }
1872
1873
1874 XImage *
1875 jwxyz_png_to_ximage (Display *dpy, Visual *visual,
1876                      const unsigned char *png_data, unsigned long data_size)
1877 {
1878   Window window = RootWindow (dpy, 0);
1879   struct running_hack *rh = window->window.rh;
1880   JNIEnv *env = rh->jni_env;
1881   jobject obj = rh->jobject;
1882   jclass    c = (*env)->GetObjectClass (env, obj);
1883   jmethodID m = (*env)->GetMethodID (env, c, "decodePNG",
1884                                      "([B)Landroid/graphics/Bitmap;");
1885   if ((*env)->ExceptionOccurred(env)) abort();
1886   jbyteArray jdata = (*env)->NewByteArray (env, data_size);
1887   (*env)->SetByteArrayRegion (env, jdata, 0,
1888                               data_size, (const jbyte *) png_data);
1889   jobject jbitmap = (
1890     m
1891     ? (*env)->CallObjectMethod (env, obj, m, jdata)
1892     : NULL);
1893   if ((*env)->ExceptionOccurred(env)) abort();
1894   (*env)->DeleteLocalRef (env, c);
1895   (*env)->DeleteLocalRef (env, jdata);
1896   if (!jbitmap)
1897     return NULL;
1898
1899   AndroidBitmapInfo bmp_info;
1900   AndroidBitmap_getInfo (env, jbitmap, &bmp_info);
1901
1902   XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL,
1903                               bmp_info.width, bmp_info.height, 8,
1904                               bmp_info.stride);
1905   char *bits = 0;
1906   AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits);
1907   img->data = (char *) calloc (img->bytes_per_line, img->height);
1908   memcpy (img->data, bits, img->bytes_per_line * img->height);
1909   AndroidBitmap_unlockPixels (env, jbitmap);
1910
1911   // Java should have returned ARGB data.
1912   // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined?
1913   if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort();
1914 # ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism.
1915 #  error Need a __BYTE_ORDER__.
1916 # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
1917   img->byte_order = img->bitmap_bit_order = LSBFirst;
1918 # elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
1919   img->byte_order = img->bitmap_bit_order = MSBFirst;
1920 # else
1921 #  error Need a __BYTE_ORDER__.
1922 # endif
1923
1924   static const union {
1925     uint8_t bytes[4];
1926     uint32_t pixel;
1927   } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}},
1928     c2 = {{0x00, 0x00, 0xff, 0x00}};
1929
1930   img->red_mask   = c0.pixel;
1931   img->green_mask = c1.pixel;
1932   img->blue_mask  = c2.pixel;
1933
1934   return img;
1935 }
1936
1937 #endif /* HAVE_ANDROID */