-/* xscreensaver, Copyright (c) 2016 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2016-2018 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
* - It is how the jwxyz.java class calls into C to run the hacks.
*/
-#include "config.h"
-
#ifdef HAVE_ANDROID /* whole file */
#include <stdarg.h>
#include <setjmp.h>
#include <GLES/gl.h>
+#include <GLES/glext.h>
#include <jni.h>
+#include <android/bitmap.h>
#include <android/log.h>
+#include <android/native_window_jni.h>
#include <pthread.h>
#include "screenhackI.h"
#include "jwxyz-android.h"
#include "textclient.h"
#include "grabscreen.h"
+#include "pow2.h"
#define countof(x) (sizeof(x)/sizeof(*(x)))
extern void check_gl_error (const char *type);
void
-do_logv(int prio, const char *fmt, va_list args)
+jwxyz_logv(Bool error, const char *fmt, va_list args)
{
- __android_log_vprint(prio, "xscreensaver", fmt, args);
+ __android_log_vprint(error ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
+ "xscreensaver", fmt, args);
/* The idea here is that if the device/emulator dies shortly after a log
message, then waiting here for a short while should increase the odds
# endif
}
-void Log(const char *fmt, ...)
-{
- va_list args;
- va_start (args, fmt);
- Logv(fmt, args);
- va_end (args);
-}
-
/* Handle an abort on Android
TODO: Test that Android handles aborts properly
*/
va_list args;
va_start (args, fmt);
- do_logv(ANDROID_LOG_ERROR, fmt, args);
+ jwxyz_logv(True, fmt, args);
va_end (args);
char buf[10240];
static pthread_mutex_t mutg = PTHREAD_MUTEX_INITIALIZER;
static void screenhack_do_fps (Display *, Window, fps_state *, void *);
+static char *get_string_resource_window (Window window, char *name);
+
+
+/* Also creates double-buffered windows. */
+static void
+create_pixmap (Window win, Drawable p)
+{
+ // See also:
+ // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
+ // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
+ // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
+
+ Assert (p->frame.width, "p->frame.width");
+ Assert (p->frame.height, "p->frame.height");
+
+ 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);
+
+ 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);
+ } 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 {
+ p->image_data = malloc (p->frame.width * p->frame.height * 4);
+ }
+}
+
+
+static void
+free_pixmap (struct running_hack *rh, Pixmap p)
+{
+ if (rh->jwxyz_gl_p) {
+ if (rh->gl_fbo_p) {
+ glDeleteTextures (1, &p->texture);
+ } else {
+ eglDestroySurface(rh->egl_display, p->egl_surface);
+ }
+ } else {
+ free (p->image_data);
+ }
+}
+
+
+static void
+prepare_context (struct running_hack *rh)
+{
+ 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);
+}
// Initialized OpenGL and runs the screenhack's init function.
//
static void
doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env,
- const struct function_table_entry *chosen, jint api,
- jobject defaults, jint w, jint h)
+ const struct function_table_entry *chosen,
+ 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 = api;
rh->jni_env = env;
rh->jobject = jwxyz_obj; // update this every time we call into C
wnd->frame.height = h;
wnd->type = WINDOW;
- rh->egl_window_ctx = eglGetCurrentContext();
- Assert(rh->egl_window_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
-
- wnd->egl_surface = eglGetCurrentSurface(EGL_DRAW);
- Assert(eglGetCurrentSurface(EGL_READ) == wnd->egl_surface,
- "doinit: EGL_READ != EGL_DRAW");
-
- rh->egl_display = eglGetCurrentDisplay();
- Assert(rh->egl_display != EGL_NO_DISPLAY, "doinit: EGL_NO_DISPLAY");
-
- EGLint config_attribs[3];
- config_attribs[0] = EGL_CONFIG_ID;
- eglQueryContext(rh->egl_display, rh->egl_window_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");
-
- rh->egl_xlib_ctx = eglCreateContext(rh->egl_display, rh->egl_config,
- EGL_NO_CONTEXT, NULL);
- Assert(rh->egl_xlib_ctx != EGL_NO_CONTEXT, "doinit: EGL_NO_CONTEXT");
- Assert(rh->egl_xlib_ctx != rh->egl_window_ctx, "Only one context here?!");
-
rh->window = wnd;
- 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
-
progclass = rh->xsft->progclass;
if ((*env)->ExceptionOccurred(env)) abort();
- jwzgles_reset();
// This has to come before resource processing. It does not do graphics.
if (rh->xsft->setup_cb)
if ((*env)->ExceptionOccurred(env)) abort();
const struct { const char *key, *val; } default_defaults[] = {
- { "doubleBuffer", "false" },
- { "multiSample", "false" },
+ { "doubleBuffer", "True" },
+ { "multiSample", "False" },
{ "texFontCacheSize", "30" },
{ "textMode", "date" },
{ "textURL",
"https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss" },
- { "grabDesktopImages", "true" },
- { "chooseRandomImages", "true" },
+ { "grabDesktopImages", "True" },
+ { "chooseRandomImages", "True" },
};
for (int i = 0; i < countof(default_defaults); i++) {
(*env)->DeleteLocalRef (env, c);
if ((*env)->ExceptionOccurred(env)) abort();
+
+ /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */
+
+ /* ####: This is lame, use a resource. */
+ 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;
+
+ if (rh->egl_p) {
+ // GL init. Must come after resource processing.
+
+ rh->egl_display = eglGetDisplay (EGL_DEFAULT_DISPLAY);
+ Assert (rh->egl_display != EGL_NO_DISPLAY, "init: EGL_NO_DISPLAY");
+
+ int egl_major, egl_minor;
+ Assert (eglInitialize (rh->egl_display, &egl_major, &egl_minor),
+ "eglInitialize failed");
+
+ // TODO: Skip depth and (probably) alpha for Xlib.
+ // TODO: Could ask for EGL_SWAP_BEHAVIOR_PRESERVED_BIT here...maybe?
+ // TODO: Probably should try to ask for EGL_PBUFFER_BIT.
+ // TODO: Do like visual-gl.c and work from a list of configs.
+ /* Probably don't need EGL_FRAMEBUFFER_TARGET_ANDROID here if GLSurfaceView
+ doesn't use it.
+ */
+ EGLint config_attribs[] = {
+ EGL_RED_SIZE, 8,
+ EGL_GREEN_SIZE, 8,
+ EGL_BLUE_SIZE, 8,
+ EGL_ALPHA_SIZE, 8,
+ EGL_DEPTH_SIZE, 16,
+ EGL_NONE
+ };
+
+ EGLint num_config;
+ Assert (eglChooseConfig (rh->egl_display, config_attribs,
+ &rh->egl_config, 1, &num_config),
+ "eglChooseConfig failed");
+ Assert (num_config == 1, "no EGL config chosen");
+
+ EGLint no_attribs[] = {EGL_NONE};
+ rh->egl_ctx = eglCreateContext (rh->egl_display, rh->egl_config,
+ EGL_NO_CONTEXT, no_attribs);
+ Assert (rh->egl_ctx != EGL_NO_CONTEXT, "init: EGL_NO_CONTEXT");
+
+ ANativeWindow *native_window =
+ ANativeWindow_fromSurface (env, jni_surface);
+
+ rh->egl_surface = eglCreateWindowSurface (rh->egl_display, rh->egl_config,
+ native_window, no_attribs);
+ Assert (rh->egl_surface != EGL_NO_SURFACE, "init: EGL_NO_SURFACE");
+
+ ANativeWindow_release (native_window);
+ } else {
+ 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;
+ }
+ }
+
+ prepare_context (rh);
+
+ if (rh->egl_p) {
+ Log ("init %s / %s / %s",
+ glGetString (GL_VENDOR),
+ glGetString (GL_RENDERER),
+ glGetString (GL_VERSION));
+ }
+
+ 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 (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;
+ }
+
+ rh->frontbuffer_p = False;
+
+ if (rh->xsft->visual == DEFAULT_VISUAL ||
+ (rh->xsft->visual == GL_VISUAL &&
+ strcmp("True", get_string_resource_window(wnd, "doubleBuffer")))) {
+
+ 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 {
+
+ if (rh->xsft->visual == DEFAULT_VISUAL)
+ create_pixmap (wnd, wnd);
+ else
+ wnd->image_data = NULL;
+
+ static const unsigned char rgba_bytes[] = {0, 1, 2, 3};
+ rh->dpy = jwxyz_image_make_display(wnd, rgba_bytes);
+
+ }
+
+ 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. */
+ 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: ;
}
#undef DEBUG_FPS
+#ifdef DEBUG_FPS
+
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+#endif
+
// Animates a single frame of the current hack.
//
-static void
+static jlong
drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
{
- double now = 0;
# ifdef DEBUG_FPS
double fps0=0, fps1=0, fps2=0, fps3=0, fps4=0;
+ fps0 = fps1 = fps2 = fps3 = fps4 = double_time();
# endif
+ unsigned long delay = 0;
+
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)
- goto END;
-
- /* Some of the screen hacks want to delay for long periods, and letting
- the framework run the update function at 30 FPS when it really wanted
- half a minute between frames would be bad. So instead, we assume that
- the framework's animation timer might fire whenever, but we only invoke
- the screen hack's "draw frame" method when enough time has expired.
- */
- struct timeval tv;
- gettimeofday (&tv, 0);
- now = tv.tv_sec + (tv.tv_usec / 1000000.0);
-# ifdef DEBUG_FPS
- fps0 = fps1 = fps2 = fps3 = fps4 = now;
-#endif
- if (now < rh->next_frame_time) 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
- gettimeofday (&tv, 0);
- fps1 = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ fps1 = double_time();
# endif
// The init function might do graphics (e.g. XClearWindow) so it has
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;
- rh->ignore_rotation_p =
- (rh->api == API_XLIB &&
+ /* ignore_rotation_p doesn't quite work at the moment. */
+ rh->ignore_rotation_p = False;
+/*
+ (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;
}
# ifdef DEBUG_FPS
- gettimeofday (&tv, 0);
- fps2 = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ fps2 = double_time();
# endif
// 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
- gettimeofday (&tv, 0);
- fps3 = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ fps3 = double_time();
# endif
- unsigned long delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure);
-
-# ifdef __arm__
- /* #### Until we work out why eglMakeCurrent is so slow on ARM. */
- if (delay <= 40000) delay = 0;
-# endif
+ delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure);
+ if (rh->jwxyz_gl_p)
+ jwxyz_gl_flush (rh->dpy);
# ifdef DEBUG_FPS
- gettimeofday (&tv, 0);
- fps4 = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ 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->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);
+ }
- gettimeofday (&tv, 0);
- now = tv.tv_sec + (tv.tv_usec / 1000000.0);
- rh->next_frame_time = now + (delay / 1000000.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);
+ }
+
+ 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: ;
(int) ((fps2-fps1)*1000000),
(int) ((fps3-fps2)*1000000),
(int) ((fps4-fps3)*1000000),
- (int) ( (now-fps4)*1000000));
+ (int) ((double_time()-fps4)*1000000));
# endif
+
+ return delay;
}
//
JNIEXPORT void JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
- jstring jhack, jint api,
- jobject defaults,
- jint w, jint h)
+ jstring jhack, jobject defaults,
+ jint w, jint h,
+ jobject jni_surface)
{
pthread_mutex_lock(&mutg);
int chosen = 0;
for (;;) {
- if (!chosen == countof(function_table)) {
+ if (chosen == countof(function_table)) {
Log ("Hack not found: %s", hack);
abort();
}
(*env)->ReleaseStringUTFChars(env, jhack, hack);
- doinit (thiz, rh, env, &function_table[chosen], api, defaults, w, h);
+ doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
+ jni_surface);
pthread_mutex_unlock(&mutg);
}
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;
wnd->frame.height = h;
}
- jwxyz_window_resized (rh->dpy);
+ 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 if (rh->xsft->visual == DEFAULT_VISUAL) {
+ free_pixmap (rh, wnd);
+ create_pixmap (wnd, wnd);
+ XClearWindow (rh->dpy, wnd); // TODO: This is lame. Copy the bits.
+ }
+
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);
}
-JNIEXPORT void JNICALL
+JNIEXPORT jlong JNICALL
Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
{
pthread_mutex_lock(&mutg);
struct running_hack *rh = getRunningHack(env, thiz);
- drawXScreenSaver(env, rh);
+ jlong result = drawXScreenSaver(env, rh);
pthread_mutex_unlock(&mutg);
+ return result;
}
if (rh->initted_p)
rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
- 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 {
+ if (rh->xsft->visual == DEFAULT_VISUAL)
+ free_pixmap (rh, rh->window);
+ if (rh->native_window)
+ ANativeWindow_release (rh->native_window);
+ }
free(rh);
(*env)->SetLongField(env, thiz, runningHackField, 0);
}
-
/***************************************************************************
Backend functions for jwxyz-gl.c
*/
-void
-prepare_context (struct running_hack *rh)
+static void
+finish_bind_drawable (Display *dpy, Drawable dst)
{
- Window w = rh->window;
- eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface,
- rh->egl_window_ctx);
- rh->current_drawable = rh->window;
+ jwxyz_assert_gl ();
+
+ glViewport (0, 0, dst->frame.width, dst->frame.height);
+ jwxyz_set_matrices (dpy, dst->frame.width, dst->frame.height, False);
+}
+
+
+static void
+bind_drawable_fbo (struct running_hack *rh, Drawable d)
+{
+ glBindFramebufferOES (GL_FRAMEBUFFER_OES,
+ d->texture ? rh->fb_pixmap : rh->fb_default);
+ if (d->texture) {
+ glFramebufferTexture2DOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
+ GL_TEXTURE_2D, d->texture, 0);
+ }
}
+
void
jwxyz_bind_drawable (Display *dpy, Window w, Drawable d)
{
JNIEnv *env = w->window.rh->jni_env;
if ((*env)->ExceptionOccurred(env)) abort();
if (rh->current_drawable != d) {
- EGLContext ctx = d == w ? rh->egl_window_ctx : rh->egl_xlib_ctx;
- eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, ctx);
- // Log("%p %p %p %p %p", rh->egl_display, d->egl_surface, ctx, w, d);
+ if (rh->gl_fbo_p) {
+ bind_drawable_fbo (rh, d);
+ } else {
+ eglMakeCurrent (rh->egl_display, d->egl_surface, d->egl_surface, rh->egl_ctx);
+ }
+ finish_bind_drawable (dpy, d);
rh->current_drawable = d;
- jwxyz_assert_gl ();
+ }
+}
- if (d != w) {
- glViewport (0, 0, d->frame.width, d->frame.height);
- jwxyz_set_matrices (dpy, d->frame.width, d->frame.height, False);
- }
+void
+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),
+ to_pow2(src->frame.height),
+ src_x, src->frame.height - src_y - height,
+ width, height, dst_x, dst_y);
+ return;
}
+
+#if 1
+ // Kumppa: 0.24 FPS
+ // Hilarious display corruption ahoy! (Note to self: it's on the emulator.)
+ // TODO for Dave: Recheck behavior on the emulator with the better Pixmap support.
+
+ rh->current_drawable = NULL;
+ if (rh->gl_fbo_p)
+ bind_drawable_fbo (rh, src);
+ else
+ eglMakeCurrent (rh->egl_display, dst->egl_surface, src->egl_surface, rh->egl_ctx);
+
+ jwxyz_gl_copy_area_read_tex_image (dpy, src->frame.height, src_x, src_y,
+ width, height, dst_x, dst_y);
+
+ if (rh->gl_fbo_p)
+ 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);
+
+#else
+ // Kumppa: 0.17 FPS
+ jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
+ width, height, dst_x, dst_y);
+#endif
+ jwxyz_assert_gl ();
+}
+
+
+void
+jwxyz_assert_drawable (Window main_window, Drawable d)
+{
+ check_gl_error("jwxyz_assert_drawable");
+}
+
+
+void
+jwxyz_assert_gl (void)
+{
+ check_gl_error("jwxyz_assert_gl");
+}
+
+
+/***************************************************************************
+ 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;
}
}
-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)
-{
-#if 0
- // Hilarious display corruption ahoy!
- jwxyz_gl_copy_area_copy_tex_image (dpy, src, dst, gc, src_x, src_y,
- width, height, dst_x, dst_y);
-#else
- jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc, src_x, src_y,
- width, height, dst_x, dst_y);
-#endif
- jwxyz_assert_gl ();
-}
-
-
-void
-jwxyz_assert_drawable (Window main_window, Drawable d)
-{
- check_gl_error("jwxyz_assert_drawable");
-}
-
-
-void
-jwxyz_assert_gl (void)
-{
- check_gl_error("jwxyz_assert_gl");
-}
-
-
Pixmap
XCreatePixmap (Display *dpy, Drawable d,
unsigned int width, unsigned int height, unsigned int depth)
{
- // See also:
- // https://web.archive.org/web/20140213220709/http://blog.vlad1.com/2010/07/01/how-to-go-mad-while-trying-to-render-to-a-texture/
- // https://software.intel.com/en-us/articles/using-opengl-es-to-accelerate-apps-with-legacy-2d-guis
- // https://www.khronos.org/registry/egl/extensions/ANDROID/EGL_ANDROID_image_native_buffer.txt
-
Window win = XRootWindow(dpy, 0);
Pixmap p = malloc(sizeof(*p));
"XCreatePixmap: bad depth");
p->pixmap.depth = depth;
- // Native EGL is Android 2.3/API 9. EGL in Java is available from API 1.
- struct running_hack *rh = win->window.rh;
- EGLint attribs[5];
- attribs[0] = EGL_WIDTH;
- attribs[1] = width;
- attribs[2] = EGL_HEIGHT;
- attribs[3] = 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");
+ 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;
}
XFreePixmap (Display *d, Pixmap p)
{
struct running_hack *rh = XRootWindow(d, 0)->window.rh;
- if (rh->current_drawable == p)
- rh->current_drawable = NULL;
- eglDestroySurface(rh->egl_display, p->egl_surface);
+
+ if (rh->jwxyz_gl_p) {
+ jwxyz_gl_flush (d);
+
+ if (rh->current_drawable == p)
+ rh->current_drawable = NULL;
+ }
+
+ free_pixmap (rh, p);
free (p);
return 0;
}
}
-char *
-get_string_resource (Display *dpy, char *name, char *class)
+static char *
+jstring_dup (JNIEnv *env, jstring str)
+{
+ Assert (str, "expected jstring, not null");
+ const char *cstr = (*env)->GetStringUTFChars (env, str, 0);
+ size_t len = (*env)->GetStringUTFLength (env, str) + 1;
+ char *result = malloc (len);
+ if (result) {
+ memcpy (result, cstr, len);
+ }
+ (*env)->ReleaseStringUTFChars (env, str, cstr);
+ return result;
+}
+
+
+static char *
+get_string_resource_window (Window window, char *name)
{
- Window window = RootWindow (dpy, 0);
JNIEnv *env = window->window.rh->jni_env;
jobject obj = window->window.rh->jobject;
(*env)->DeleteLocalRef (env, c);
(*env)->DeleteLocalRef (env, jstr);
char *ret = 0;
- if (jvalue) {
- const char *cvalue = (*env)->GetStringUTFChars (env, jvalue, 0);
- ret = strdup (cvalue);
- (*env)->ReleaseStringUTFChars (env, jvalue, cvalue);
- }
+ if (jvalue)
+ ret = jstring_dup (env, jvalue);
Log("pref %s = %s", name, (ret ? ret : "(null)"));
return ret;
}
+char *
+get_string_resource (Display *dpy, char *name, char *class)
+{
+ return get_string_resource_window (RootWindow (dpy, 0), name);
+}
+
+
/* Returns the contents of the URL. */
char *
textclient_mobile_url_string (Display *dpy, const char *url)
}
+float
+jwxyz_scale (Window main_window)
+{
+ // TODO: Use the actual device resolution.
+ return 2;
+}
+
+
+const char *
+jwxyz_default_font_family (int require)
+{
+ /* Font families in XLFDs are totally ignored (for now). */
+ return "sans-serif";
+}
+
+
void *
-jwxyz_load_native_font (Display *dpy, const char *name,
- char **native_name_ret, float *size_ret,
+jwxyz_load_native_font (Window window,
+ int traits_jwxyz, int mask_jwxyz,
+ const char *font_name_ptr, size_t font_name_length,
+ int font_name_type, float size,
+ char **family_name_ret,
int *ascent_ret, int *descent_ret)
{
- Window window = RootWindow (dpy, 0);
JNIEnv *env = window->window.rh->jni_env;
jobject obj = window->window.rh->jobject;
- jstring jstr = (*env)->NewStringUTF (env, name);
+ jstring jname = NULL;
+ if (font_name_ptr) {
+ char *name_nul = malloc(font_name_length + 1);
+ memcpy(name_nul, font_name_ptr, font_name_length);
+ name_nul[font_name_length] = 0;
+ jname = (*env)->NewStringUTF (env, name_nul);
+ free(name_nul);
+ }
+
jclass c = (*env)->GetObjectClass (env, obj);
jmethodID m = (*env)->GetMethodID (env, c, "loadFont",
- "(Ljava/lang/String;)[Ljava/lang/Object;");
+ "(IILjava/lang/String;IF)[Ljava/lang/Object;");
if ((*env)->ExceptionOccurred(env)) abort();
jobjectArray array = (m
- ? (*env)->CallObjectMethod (env, obj, m, jstr)
+ ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
+ (jint)traits_jwxyz, jname,
+ (jint)font_name_type, (jfloat)size)
: NULL);
+
(*env)->DeleteLocalRef (env, c);
- (*env)->DeleteLocalRef (env, jstr);
- jstr = 0;
if (array) {
jobject font = (*env)->GetObjectArrayElement (env, array, 0);
- jobject name = (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
- jobject size = (*env)->GetObjectArrayElement (env, array, 2);
- jobject asc = (*env)->GetObjectArrayElement (env, array, 3);
- jobject desc = (*env)->GetObjectArrayElement (env, array, 4);
+ jobject family_name =
+ (jstring) ((*env)->GetObjectArrayElement (env, array, 1));
+ jobject asc = (*env)->GetObjectArrayElement (env, array, 2);
+ jobject desc = (*env)->GetObjectArrayElement (env, array, 3);
if ((*env)->ExceptionOccurred(env)) abort();
- const char *cname = (*env)->GetStringUTFChars (env, name, 0);
- *native_name_ret = strdup (cname);
- (*env)->ReleaseStringUTFChars (env, name, cname);
+ if (family_name_ret)
+ *family_name_ret = jstring_dup (env, family_name);
- c = (*env)->GetObjectClass(env, font);
- m = (*env)->GetMethodID (env, c, "longValue", "()J");
- long font_id = (*env)->CallLongMethod (env, font, m);
+ jobject paint = (*env)->NewGlobalRef (env, font);
if ((*env)->ExceptionOccurred(env)) abort();
- c = (*env)->GetObjectClass(env, size);
+ c = (*env)->GetObjectClass(env, asc);
m = (*env)->GetMethodID (env, c, "floatValue", "()F");
if ((*env)->ExceptionOccurred(env)) abort();
- *size_ret = (*env)->CallFloatMethod (env, size, m);
*ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
*descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
- return (void *) font_id;
+ return (void *) paint;
} else {
return 0;
}
{
Window window = RootWindow (dpy, 0);
JNIEnv *env = window->window.rh->jni_env;
- jobject obj = window->window.rh->jobject;
if ((*env)->ExceptionOccurred(env)) abort();
- jclass c = (*env)->GetObjectClass (env, obj);
- jmethodID m = (*env)->GetMethodID (env, c, "releaseFont", "(J)V");
- (*env)->CallVoidMethod (env, obj, m, (jobject) native_font);
+ (*env)->DeleteGlobalRef (env, (jobject) native_font);
if ((*env)->ExceptionOccurred(env)) abort();
}
//
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);
jstring jstr = (*env)->NewStringUTF (env, s2);
jclass c = (*env)->GetObjectClass (env, obj);
jmethodID m = (*env)->GetMethodID (env, c, "renderText",
- "(JLjava/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,
- (jlong) (long) native_font,
+ (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);
}
+char *
+jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
+{
+ JNIEnv *env = XRootWindow (dpy, 0)->window.rh->jni_env;
+ /* FindClass doesn't like to load classes if GetStaticMethodID fails. Huh? */
+ jclass
+ c = (*env)->FindClass (env, "java/lang/Character"),
+ c2 = (*env)->FindClass (env, "java/lang/NoSuchMethodError");
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+ jmethodID m = (*env)->GetStaticMethodID (
+ env, c, "getName", "(I)Ljava/lang/String;");
+ jthrowable exc = (*env)->ExceptionOccurred(env);
+ if (exc) {
+ if ((*env)->IsAssignableFrom(env, (*env)->GetObjectClass(env, exc), c2)) {
+ (*env)->ExceptionClear (env);
+ Assert (!m, "jwxyz_unicode_character_name: m?");
+ } else {
+ abort();
+ }
+ }
+
+ char *ret = NULL;
+
+ if (m) {
+ jstring name = (*env)->CallStaticObjectMethod (env, c, m, (jint)uc);
+ if (name)
+ ret = jstring_dup (env, name);
+ }
+
+ if (!ret) {
+ asprintf(&ret, "U+%.4lX", uc);
+ }
+
+ return ret;
+}
+
+
/* 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)
{
Window window = RootWindow (dpy, 0);
struct running_hack *rh = window->window.rh;
(grab_p
? "getScreenshot"
: "loadRandomImage"),
- "(IIZ)Ljava/nio/ByteBuffer;");
+ "(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 */