X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=jwxyz%2Fjwxyz-android.c;h=2b74b799160aa10f178660915cecd803151838f0;hb=c85f503f5793839a6be4c818332aca4a96927bb2;hp=3c85d253b579b88e37da88abfa047fc820b36283;hpb=d6b0217f2417bd19187f0ebc389d6c5c2233b11c;p=xscreensaver diff --git a/jwxyz/jwxyz-android.c b/jwxyz/jwxyz-android.c index 3c85d253..2b74b799 100644 --- a/jwxyz/jwxyz-android.c +++ b/jwxyz/jwxyz-android.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2016 Jamie Zawinski +/* xscreensaver, Copyright (c) 2016-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,8 +16,6 @@ * - It is how the jwxyz.java class calls into C to run the hacks. */ -#include "config.h" - #ifdef HAVE_ANDROID /* whole file */ #include @@ -28,8 +26,11 @@ #include #include +#include #include +#include #include +#include #include #include "screenhackI.h" @@ -38,6 +39,7 @@ #include "jwxyz-android.h" #include "textclient.h" #include "grabscreen.h" +#include "pow2.h" #define countof(x) (sizeof(x)/sizeof(*(x))) @@ -143,20 +145,100 @@ static jmethodID entryGetKey, entryGetValue; 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 @@ -171,41 +253,10 @@ doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env, 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) @@ -223,14 +274,14 @@ doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env, 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++) { @@ -290,6 +341,171 @@ doinit (jobject jwxyz_obj, struct running_hack *rh, JNIEnv *env, (*env)->DeleteLocalRef (env, c); if ((*env)->ExceptionOccurred(env)) abort(); + + /* Note: https://source.android.com/devices/graphics/arch-egl-opengl */ + + /* ####: 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: ; } @@ -328,15 +544,24 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh) if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return. - /* There is some kind of weird redisplay race condition between Settings - and the launching hack: e.g., Abstractile does XClearWindow at init, - but the screen is getting filled with random bits. So let's wait a - few frames before really starting up. - */ - if (++rh->frame_count < 8) - goto END; + Window wnd = rh->window; - prepare_context(rh); + prepare_context (rh); + + if (rh->egl_p) { + /* There is some kind of weird redisplay race condition between Settings + and the launching hack: e.g., Abstractile does XClearWindow at init, + but the screen is getting filled with random bits. So let's wait a + few frames before really starting up. + + TODO: Is this still true? + */ + if (++rh->frame_count < 8) { + /* glClearColor (1.0, 0.0, 1.0, 0.0); */ + glClear (GL_COLOR_BUFFER_BIT); /* We always need to draw *something*. */ + goto END; + } + } # ifdef DEBUG_FPS fps1 = double_time(); @@ -350,20 +575,25 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh) void *(*init_cb) (Display *, Window, void *) = (void *(*)(Display *, Window, void *)) rh->xsft->init_cb; - unsigned int bg = - get_pixel_resource (rh->dpy, 0, "background", "Background"); - XSetWindowBackground (rh->dpy, rh->window, bg); - XClearWindow (rh->dpy, rh->window); + if (rh->xsft->visual == DEFAULT_VISUAL) { + unsigned int bg = + get_pixel_resource (rh->dpy, 0, "background", "Background"); + XSetWindowBackground (rh->dpy, wnd, bg); + XClearWindow (rh->dpy, wnd); + } - rh->closure = init_cb (rh->dpy, rh->window, rh->xsft->setup_arg); + rh->closure = init_cb (rh->dpy, wnd, rh->xsft->setup_arg); rh->initted_p = True; - 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; @@ -379,19 +609,65 @@ drawXScreenSaver (JNIEnv *env, struct running_hack *rh) // Apparently events don't come in on the drawing thread, and JNI flips // out. So we queue them there and run them here. + // TODO: Events should be coming in on the drawing thread now, so dump this. send_queued_events (rh); # ifdef DEBUG_FPS fps3 = double_time(); # endif - delay = rh->xsft->draw_cb(rh->dpy, rh->window, rh->closure); + delay = rh->xsft->draw_cb(rh->dpy, wnd, rh->closure); + + if (rh->jwxyz_gl_p) + jwxyz_gl_flush (rh->dpy); # ifdef DEBUG_FPS fps4 = double_time(); # endif if (rh->fpst && rh->xsft->fps_cb) - rh->xsft->fps_cb (rh->dpy, rh->window, rh->fpst, rh->closure); + rh->xsft->fps_cb (rh->dpy, wnd, rh->fpst, rh->closure); + + if (rh->egl_p) { + if (rh->jwxyz_gl_p && rh->frontbuffer_p) { + jwxyz_gl_copy_area (rh->dpy, wnd, &rh->frontbuffer, rh->copy_gc, + 0, 0, wnd->frame.width, wnd->frame.height, + 0, 0); + } + + // Getting failure here before/during/after resize, sometimes. Log sez: + // W/Adreno-EGLSUB(18428): : 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: ; @@ -439,9 +715,9 @@ acquireClass (JNIEnv *env, const char *className, jobject *globalRef) // 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); @@ -501,7 +777,8 @@ Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz, (*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); } @@ -520,14 +797,23 @@ Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz, struct running_hack *rh = getRunningHack(env, thiz); + prepare_context (rh); + + if (rh->egl_p) { + glViewport (0, 0, w, h); + } else { + int result = ANativeWindow_setBuffersGeometry (rh->native_window, w, h, + WINDOW_FORMAT_RGBX_8888); + if (result < 0) + Log ("failed to resize surface (%d)", result); + } + Window wnd = rh->window; wnd->frame.x = 0; wnd->frame.y = 0; wnd->frame.width = w; wnd->frame.height = h; - glViewport (0, 0, w, h); - if (ignore_rotation_p(rh->dpy) && rot != 0 && rot != 180 && rot != -180) { int swap = w; @@ -537,12 +823,28 @@ Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz, 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); @@ -577,7 +879,29 @@ Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz) 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); @@ -725,20 +1049,32 @@ Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz, } - /*************************************************************************** Backend functions for jwxyz-gl.c */ -void -prepare_context (struct running_hack *rh) +static void +finish_bind_drawable (Display *dpy, Drawable dst) +{ + 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) { - Window w = rh->window; - eglMakeCurrent (rh->egl_display, w->egl_surface, w->egl_surface, - rh->egl_window_ctx); - rh->current_drawable = rh->window; + 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) { @@ -746,17 +1082,103 @@ 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 (dpy, gc, GL_TEXTURE_2D, to_pow2(src->frame.width), + to_pow2(src->frame.height), + src_x, src->frame.height - src_y - height, + jwxyz_drawable_depth (src), width, height, + dst_x, dst_y, False); + 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; } @@ -797,46 +1219,10 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) } -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)); @@ -850,22 +1236,14 @@ XCreatePixmap (Display *dpy, Drawable d, "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; } @@ -875,9 +1253,15 @@ int 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; } @@ -912,10 +1296,9 @@ jstring_dup (JNIEnv *env, jstring str) } -char * -get_string_resource (Display *dpy, char *name, char *class) +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; @@ -940,6 +1323,13 @@ get_string_resource (Display *dpy, char *name, char *class) } +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) @@ -977,46 +1367,73 @@ 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(); - *native_name_ret = jstring_dup (env, name); + if (family_name_ret) + *family_name_ret = jstring_dup (env, family_name); 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); @@ -1057,7 +1474,7 @@ static void dump_reference_tables(JNIEnv *env) // void jwxyz_render_text (Display *dpy, void *native_font, - const char *str, size_t len, int utf8, + const char *str, size_t len, Bool utf8, Bool antialias_p, XCharStruct *cs, char **pixmap_ret) { Window window = RootWindow (dpy, 0); @@ -1089,14 +1506,15 @@ jwxyz_render_text (Display *dpy, void *native_font, jstring jstr = (*env)->NewStringUTF (env, s2); jclass c = (*env)->GetObjectClass (env, obj); jmethodID m = (*env)->GetMethodID (env, c, "renderText", - "(Landroid/graphics/Paint;Ljava/lang/String;Z)Ljava/nio/ByteBuffer;"); + "(Landroid/graphics/Paint;Ljava/lang/String;ZZ)Ljava/nio/ByteBuffer;"); if ((*env)->ExceptionOccurred(env)) abort(); jobject buf = (m ? (*env)->CallObjectMethod (env, obj, m, (jobject) native_font, jstr, - (pixmap_ret ? JNI_TRUE : JNI_FALSE)) + (pixmap_ret ? JNI_TRUE : JNI_FALSE), + antialias_p) : NULL); (*env)->DeleteLocalRef (env, c); (*env)->DeleteLocalRef (env, jstr); @@ -1172,9 +1590,7 @@ jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc) /* Called from utils/grabclient.c */ char * -jwxyz_load_random_image (Display *dpy, - int *width_ret, int *height_ret, - char **name_ret) +jwxyz_draw_random_image (Display *dpy, Drawable drawable, GC gc) { Window window = RootWindow (dpy, 0); struct running_hack *rh = window->window.rh; @@ -1200,43 +1616,109 @@ jwxyz_load_random_image (Display *dpy, jmethodID m = (*env)->GetMethodID (env, c, (grab_p ? "getScreenshot" - : "loadRandomImage"), - "(IIZ)Ljava/nio/ByteBuffer;"); + : "checkThenLoadRandomImage"), + "(IIZ)[Ljava/lang/Object;"); + if ((*env)->ExceptionOccurred(env)) abort(); + jobjectArray img_name = ( + m + ? (*env)->CallObjectMethod (env, obj, m, + drawable->frame.width, drawable->frame.height, + (rotate_p ? JNI_TRUE : JNI_FALSE)) + : NULL); if ((*env)->ExceptionOccurred(env)) abort(); - jobject buf = (m - ? (*env)->CallObjectMethod (env, obj, m, - window->frame.width, - window->frame.height, - (rotate_p ? JNI_TRUE : JNI_FALSE)) - : NULL); (*env)->DeleteLocalRef (env, c); - unsigned char *bits = (unsigned char *) - (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0); - - if (bits) { - int i = 0; - int L = (*env)->GetDirectBufferCapacity (env, buf); - if (L < 100) abort(); - int width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; - int height = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2; - char *name = (char *) bits + i; - int L2 = strlen (name); - i += L2 + 1; - if (width * height * 4 != L - i) abort(); - char *pix = malloc (L - i); - if (! pix) abort(); - memcpy (pix, bits + i, L - i); - *width_ret = width; - *height_ret = height; - *name_ret = strdup (name); - return (char *) pix; + if (!img_name) { + fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image")); + return NULL; } - if (buf) - (*env)->DeleteLocalRef (env, buf); + jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 0); - return 0; + AndroidBitmapInfo bmp_info; + AndroidBitmap_getInfo (env, jbitmap, &bmp_info); + + XImage *img = XCreateImage (dpy, NULL, visual_depth(NULL, NULL), + ZPixmap, 0, NULL, + bmp_info.width, bmp_info.height, 0, + bmp_info.stride); + + AndroidBitmap_lockPixels (env, jbitmap, (void **) &img->data); + + XPutImage (dpy, drawable, gc, img, 0, 0, + (drawable->frame.width - bmp_info.width) / 2, + (drawable->frame.height - bmp_info.height) / 2, + bmp_info.width, bmp_info.height); + + AndroidBitmap_unlockPixels (env, jbitmap); + img->data = NULL; + XDestroyImage (img); + + return jstring_dup (env, (*env)->GetObjectArrayElement (env, img_name, 1)); +} + + +XImage * +jwxyz_png_to_ximage (Display *dpy, Visual *visual, + const unsigned char *png_data, unsigned long data_size) +{ + Window window = RootWindow (dpy, 0); + struct running_hack *rh = window->window.rh; + JNIEnv *env = rh->jni_env; + jobject obj = rh->jobject; + jclass c = (*env)->GetObjectClass (env, obj); + jmethodID m = (*env)->GetMethodID (env, c, "decodePNG", + "([B)Landroid/graphics/Bitmap;"); + if ((*env)->ExceptionOccurred(env)) abort(); + jbyteArray jdata = (*env)->NewByteArray (env, data_size); + (*env)->SetByteArrayRegion (env, jdata, 0, + data_size, (const jbyte *) png_data); + jobject jbitmap = ( + m + ? (*env)->CallObjectMethod (env, obj, m, jdata) + : NULL); + if ((*env)->ExceptionOccurred(env)) abort(); + (*env)->DeleteLocalRef (env, c); + (*env)->DeleteLocalRef (env, jdata); + if (!jbitmap) + return NULL; + + AndroidBitmapInfo bmp_info; + AndroidBitmap_getInfo (env, jbitmap, &bmp_info); + + XImage *img = XCreateImage (dpy, NULL, 32, ZPixmap, 0, NULL, + bmp_info.width, bmp_info.height, 8, + bmp_info.stride); + char *bits = 0; + AndroidBitmap_lockPixels (env, jbitmap, (void **) &bits); + img->data = (char *) calloc (img->bytes_per_line, img->height); + memcpy (img->data, bits, img->bytes_per_line * img->height); + AndroidBitmap_unlockPixels (env, jbitmap); + + // Java should have returned ARGB data. + // WTF, why isn't ANDROID_BITMAP_FORMAT_ARGB_8888 defined? + if (bmp_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) abort(); +# ifndef __BYTE_ORDER__ // A GCC (and Clang)-ism. +# error Need a __BYTE_ORDER__. +# elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + img->byte_order = img->bitmap_bit_order = LSBFirst; +# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + img->byte_order = img->bitmap_bit_order = MSBFirst; +# else +# error Need a __BYTE_ORDER__. +# endif + + static const union { + uint8_t bytes[4]; + uint32_t pixel; + } c0 = {{0xff, 0x00, 0x00, 0x00}}, c1 = {{0x00, 0xff, 0x00, 0x00}}, + c2 = {{0x00, 0x00, 0xff, 0x00}}; + + img->red_mask = c0.pixel; + img->green_mask = c1.pixel; + img->blue_mask = c2.pixel; + + return img; } #endif /* HAVE_ANDROID */