summaryrefslogtreecommitdiffstats
path: root/jwxyz
diff options
context:
space:
mode:
authorSimon Rettberg2018-10-16 10:08:48 +0200
committerSimon Rettberg2018-10-16 10:08:48 +0200
commitd3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch)
treecbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /jwxyz
downloadxscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz
xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip
Original 5.40
Diffstat (limited to 'jwxyz')
-rw-r--r--jwxyz/Makefile.in141
-rw-r--r--jwxyz/README30
-rw-r--r--jwxyz/jwxyz-android.c1724
-rw-r--r--jwxyz/jwxyz-android.h121
-rw-r--r--jwxyz/jwxyz-cocoa.h98
-rw-r--r--jwxyz/jwxyz-cocoa.m1043
-rw-r--r--jwxyz/jwxyz-common.c1828
-rw-r--r--jwxyz/jwxyz-gl.c2125
-rw-r--r--jwxyz/jwxyz-image.c527
-rw-r--r--jwxyz/jwxyz-timers.c357
-rw-r--r--jwxyz/jwxyz-timers.h25
-rw-r--r--jwxyz/jwxyz.h906
-rw-r--r--jwxyz/jwxyz.m1812
-rw-r--r--jwxyz/jwxyzI.h208
-rw-r--r--jwxyz/jwzgles.c4331
-rw-r--r--jwxyz/jwzgles.h520
-rw-r--r--jwxyz/jwzglesI.h355
17 files changed, 16151 insertions, 0 deletions
diff --git a/jwxyz/Makefile.in b/jwxyz/Makefile.in
new file mode 100644
index 0000000..0ba48a6
--- /dev/null
+++ b/jwxyz/Makefile.in
@@ -0,0 +1,141 @@
+# utils/Makefile.in --- xscreensaver, Copyright (c) 1997-2010 Jamie Zawinski.
+# the `../configure' script generates `jwxyz/Makefile' from this file.
+
+# JWXYZ Is Not Xlib.
+#
+# But it's a bunch of function definitions that bear some resemblance to
+# Xlib and that kinda sorta implement Xlib in terms of the native graphics
+# substrate (Cocoa, OpenGL, GLES, Java).
+
+@SET_MAKE@
+.SUFFIXES:
+.SUFFIXES: .c .o
+
+srcdir = @srcdir@
+VPATH = @srcdir@
+prefix = @prefix@
+datarootdir = @datarootdir@
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+DEFS = @DEFS@
+
+DEPEND = @DEPEND@
+DEPEND_FLAGS = @DEPEND_FLAGS@
+DEPEND_DEFINES = @DEPEND_DEFINES@
+
+SHELL = /bin/sh
+INSTALL = @INSTALL@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_DIRS = @INSTALL_DIRS@
+
+X_CFLAGS = @X_CFLAGS@
+
+INCLUDES_1 = -I$(srcdir) -I.. -I../utils
+INCLUDES = $(INCLUDES_1) @INCLUDES@
+
+SRCS = jwxyz-android.c jwxyz-cocoa.m jwxyz-common.c jwxyz-gl.c \
+ jwxyz-timers.c jwxyz-image.c jwxyz.m jwzgles.c
+OBJS =
+HDRS = jwxyz-android.h jwxyz-cocoa.h jwxyz-timers.h jwxyz.h \
+ jwxyzI.h jwzgles.h jwzglesI.h
+EXTRAS = README Makefile.in
+
+TARFILES = $(EXTRAS) $(SRCS) $(HDRS) $(LOGOS)
+
+
+default: all
+all: $(OBJS)
+
+install: install-program install-man
+uninstall: uninstall-program uninstall-man
+
+install-strip:
+ $(MAKE) INSTALL_PROGRAM='$(INSTALL_PROGRAM) -s' install
+
+install-program:
+install-man:
+uninstall-program:
+uninstall-man:
+
+clean:
+ -rm -f *.o a.out core
+
+distclean: clean
+ -rm -f Makefile TAGS *~ "#"*
+
+# Adds all current dependencies to Makefile
+depend:
+ $(DEPEND) -s '# DO NOT DELETE: updated by make depend' \
+ $(DEPEND_FLAGS) -- \
+ $(INCLUDES) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \
+ $(SRCS)
+
+# Adds some dependencies to Makefile.in -- not totally accurate, but pretty
+# close. This excludes dependencies on files in /usr/include, etc. It tries
+# to include only dependencies on files which are themselves a part of this
+# package.
+distdepend::
+ @echo updating dependencies in `pwd`/Makefile.in... ; \
+ $(DEPEND) -w 0 -f - \
+ -s '# DO NOT DELETE: updated by make distdepend' $(DEPEND_FLAGS) -- \
+ $(INCLUDES_1) $(DEFS) $(DEPEND_DEFINES) $(CFLAGS) $(X_CFLAGS) -- \
+ $(SRCS) 2>/dev/null | \
+ sort -d | \
+ ( \
+ awk '/^# .*Makefile.in ---/,/^# DO .*distdepend/' < Makefile.in ; \
+ sed -e '/^#.*/d' \
+ -e 's@ \./@ @g;s@ /[^ ]*@@g;/^.*:$$/d' \
+ -e 's@ \([^$$]\)@ $$(srcdir)/\1@g' \
+ -e 's@ $$(srcdir)/\(.*config.h\)@ \1@g' ; \
+ echo '' \
+ ) > /tmp/distdepend.$$$$ && \
+ mv /tmp/distdepend.$$$$ Makefile.in
+
+TAGS: tags
+tags:
+ find $(srcdir) -name '*.[chly]' -print | xargs etags -a
+
+echo_tarfiles:
+ @echo $(TARFILES)
+
+
+# How we build object files in this directory.
+.c.o:
+ $(CC) -c $(INCLUDES) $(DEFS) $(CPPFLAGS) $(CFLAGS) $(X_CFLAGS) $<
+
+
+# Rules for generating the VMS makefiles on Unix, so that it doesn't have to
+# be done by hand...
+#
+VMS_AXP_COMPILE=$$ CC/DECC/PREFIX=ALL/DEFINE=(VMS,HAVE_CONFIG_H)/INCL=([],[-])
+
+compile_axp.com: Makefile.in
+ @echo generating $@ from $<... ; \
+ ( ( for c in $(SRCS) vms-*.c ; do \
+ c=`echo $$c | tr a-z A-Z` ; \
+ echo "$(VMS_AXP_COMPILE) $$c" ; \
+ done ; \
+ ) | sort -d ; \
+ echo '$$ lib/cre utils.olb_axp' ; \
+ echo '$$ lib utils.olb_axp *.obj' ; \
+ echo '$$! delete/noconf *.obj;' ; \
+ ) > $@
+
+compile_decc.com: compile_axp.com
+ @echo generating $@ from $<... ; \
+ sed 's/axp/decc/g' < $< > $@
+
+distdepend:: compile_axp.com compile_decc.com
+
+
+##############################################################################
+#
+# DO NOT DELETE: updated by make distdepend
+
+jwxyz-common.o: ../config.h
+jwxyz-image.o: ../config.h
+jwxyz-timers.o: ../config.h
+jwzgles.o: ../config.h
+
diff --git a/jwxyz/README b/jwxyz/README
new file mode 100644
index 0000000..7c350bb
--- /dev/null
+++ b/jwxyz/README
@@ -0,0 +1,30 @@
+JWXYZ Is Not Xlib.
+
+But it's a bunch of function definitions that bear some resemblance to
+Xlib and that kinda sorta implement Xlib in terms of the native graphics
+substrate (Cocoa, OpenGL, GLES, Java).
+
+When porting XScreenSaver to other platforms, my goal is to keep a single
+code base that compiles for multiple platforms. That is, I don't want to
+end up with two different files that implement "Attraction" using different
+APIs or different languages,
+
+Since the vast majority of xscreensaver was originally written in C for
+the vintage-1985 X11 API and the vintage-1992 OpenGL API, this presents
+something of a challenge.
+
+ 1: To do the MacOS port, I implemented X11 in terms of Cocoa.
+ That's what jwxyz.m is.
+
+ 2: To do the iOS port, I used that X11/Cocoa layer from #1, but also
+ had to implement OpenGL 1.1 in terms of OpenGLES 1.0. That's what
+ jwzgles.c is. I have some things to say about that. You can
+ read it on my blog: http://jwz.org/b/yhM9
+
+ 3: To do the Android port, we used the OpenGL/OpenGLES layer from #2,
+ but implemented X11 in terms of OpenGL. That's what jwxyz-gl.c,
+ jwxyz-common.c and jwxyz-android.c are.
+
+Perhaps some day we can re-target MacOS and iOS at the OpenGL port of X11
+instead of the Cocoa port of X11, and replace jwxyz.m with jwxyz-gl.c and
+jwxyz-cocoa.m. That day has not yet arrived.
diff --git a/jwxyz/jwxyz-android.c b/jwxyz/jwxyz-android.c
new file mode 100644
index 0000000..2b74b79
--- /dev/null
+++ b/jwxyz/jwxyz-android.c
@@ -0,0 +1,1724 @@
+/* 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * This file is three related things:
+ *
+ * - It is the Android-specific C companion to jwxyz-gl.c;
+ * - It is how C calls into Java to do things that OpenGL does not
+ * have access to without Java-based APIs;
+ * - It is how the jwxyz.java class calls into C to run the hacks.
+ */
+
+#ifdef HAVE_ANDROID /* whole file */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <math.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 "jwxyzI.h"
+#include "jwzglesI.h"
+#include "jwxyz-android.h"
+#include "textclient.h"
+#include "grabscreen.h"
+#include "pow2.h"
+
+
+#define countof(x) (sizeof(x)/sizeof(*(x)))
+
+extern struct xscreensaver_function_table *xscreensaver_function_table;
+
+struct function_table_entry {
+ const char *progname;
+ struct xscreensaver_function_table *xsft;
+};
+
+#include "gen/function-table.h"
+
+struct event_queue {
+ XEvent event;
+ struct event_queue *next;
+};
+
+static void send_queued_events (struct running_hack *rh);
+
+const char *progname;
+const char *progclass;
+int mono_p = 0;
+
+static JavaVM *global_jvm;
+static jmp_buf jmp_target;
+
+static double current_rotation = 0;
+
+extern void check_gl_error (const char *type);
+
+void
+jwxyz_logv(Bool error, const char *fmt, va_list 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
+ that adb logcat can pick up the message before everything blows up. Of
+ course, doing this means dumping a ton of messages will slow things down
+ significantly.
+ */
+# if 0
+ struct timespec ts;
+ ts.tv_sec = 0;
+ ts.tv_nsec = 25 * 1000000;
+ nanosleep(&ts, NULL);
+# endif
+}
+
+/* Handle an abort on Android
+ TODO: Test that Android handles aborts properly
+ */
+void
+jwxyz_abort (const char *fmt, ...)
+{
+ /* Send error to Android device log */
+ if (!fmt || !*fmt)
+ fmt = "abort";
+
+ va_list args;
+ va_start (args, fmt);
+ jwxyz_logv(True, fmt, args);
+ va_end (args);
+
+ char buf[10240];
+ va_start (args, fmt);
+ vsprintf (buf, fmt, args);
+ va_end (args);
+
+ JNIEnv *env;
+ (*global_jvm)->AttachCurrentThread (global_jvm, &env, NULL);
+
+ if (! (*env)->ExceptionOccurred(env)) {
+ // If there's already an exception queued, let's just go with that one.
+ // Else, queue a Java exception to be thrown.
+ (*env)->ThrowNew (env, (*env)->FindClass(env, "java/lang/RuntimeException"),
+ buf);
+ }
+
+ // Nonlocal exit out of the jwxyz code.
+ longjmp (jmp_target, 1);
+}
+
+
+/* We get to keep live references to Java classes in use because the VM can
+ unload a class that isn't being used, which invalidates field and method
+ IDs.
+ https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/design.html#wp17074
+*/
+
+
+// #### only need one var I think
+static size_t classRefCount = 0;
+static jobject globalRefjwxyz, globalRefIterable, globalRefIterator,
+ globalRefMapEntry;
+
+static jfieldID runningHackField;
+static jmethodID iterableIterator, iteratorHasNext, iteratorNext;
+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,
+ 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->jni_env = env;
+ rh->jobject = jwxyz_obj; // update this every time we call into C
+
+ (*env)->GetJavaVM (env, &global_jvm);
+
+# undef ya_rand_init // This is the one and only place it is allowed
+ ya_rand_init (0);
+
+ Window wnd = (Window) calloc(1, sizeof(*wnd));
+ wnd->window.rh = rh;
+ wnd->frame.width = w;
+ wnd->frame.height = h;
+ wnd->type = WINDOW;
+
+ rh->window = wnd;
+ progclass = rh->xsft->progclass;
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ // This has to come before resource processing. It does not do graphics.
+ if (rh->xsft->setup_cb)
+ rh->xsft->setup_cb(rh->xsft, rh->xsft->setup_arg);
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ // Load the defaults.
+ // Unceremoniously stolen from [PrefsReader defaultsToDict:].
+
+ jclass c = (*env)->GetObjectClass (env, defaults);
+ jmethodID m = (*env)->GetMethodID (env, c, "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
+ if (! m) abort();
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ const struct { const char *key, *val; } default_defaults[] = {
+ { "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" },
+ };
+
+ for (int i = 0; i < countof(default_defaults); i++) {
+ const char *key = default_defaults[i].key;
+ const char *val = default_defaults[i].val;
+ char *key2 = malloc (strlen(progname) + strlen(key) + 2);
+ strcpy (key2, progname);
+ strcat (key2, "_");
+ strcat (key2, key);
+
+ // defaults.put(key2, val);
+ jstring jkey = (*env)->NewStringUTF (env, key2);
+ jstring jval = (*env)->NewStringUTF (env, val);
+ (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
+ (*env)->DeleteLocalRef (env, jkey);
+ (*env)->DeleteLocalRef (env, jval);
+ // Log ("default0: \"%s\" = \"%s\"", key2, val);
+ free (key2);
+ }
+
+ const char *const *defs = rh->xsft->defaults;
+ while (*defs) {
+ char *line = strdup (*defs);
+ char *key, *val;
+ key = line;
+ while (*key == '.' || *key == '*' || *key == ' ' || *key == '\t')
+ key++;
+ val = key;
+ while (*val && *val != ':')
+ val++;
+ if (*val != ':') abort();
+ *val++ = 0;
+ while (*val == ' ' || *val == '\t')
+ val++;
+
+ unsigned long L = strlen(val);
+ while (L > 0 && (val[L-1] == ' ' || val[L-1] == '\t'))
+ val[--L] = 0;
+
+ char *key2 = malloc (strlen(progname) + strlen(key) + 2);
+ strcpy (key2, progname);
+ strcat (key2, "_");
+ strcat (key2, key);
+
+ // defaults.put(key2, val);
+ jstring jkey = (*env)->NewStringUTF (env, key2);
+ jstring jval = (*env)->NewStringUTF (env, val);
+ (*env)->CallObjectMethod (env, defaults, m, jkey, jval);
+ (*env)->DeleteLocalRef (env, jkey);
+ (*env)->DeleteLocalRef (env, jval);
+ // Log ("default: \"%s\" = \"%s\"", key2, val);
+ free (key2);
+ free (line);
+ defs++;
+ }
+
+ (*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 jlong
+drawXScreenSaver (JNIEnv *env, struct running_hack *rh)
+{
+# 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.
+
+ Window wnd = rh->window;
+
+ 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();
+# endif
+
+ // The init function might do graphics (e.g. XClearWindow) so it has
+ // to be run from inside onDrawFrame, not onSurfaceChanged.
+
+ if (! rh->initted_p) {
+
+ void *(*init_cb) (Display *, Window, void *) =
+ (void *(*)(Display *, Window, void *)) rh->xsft->init_cb;
+
+ 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, wnd, rh->xsft->setup_arg);
+ rh->initted_p = True;
+
+ /* ignore_rotation_p doesn't quite work at the moment. */
+ rh->ignore_rotation_p = False;
+/*
+ (rh->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, wnd);
+ if (! rh->xsft->fps_cb) rh->xsft->fps_cb = screenhack_do_fps;
+ } else {
+ rh->fpst = NULL;
+ rh->xsft->fps_cb = 0;
+ }
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+ }
+
+# ifdef DEBUG_FPS
+ 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
+ fps3 = double_time();
+# endif
+
+ 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, 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): <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: ;
+
+# ifdef DEBUG_FPS
+ Log("## FPS prep = %-6d init = %-6d events = %-6d draw = %-6d fps = %-6d\n",
+ (int) ((fps1-fps0)*1000000),
+ (int) ((fps2-fps1)*1000000),
+ (int) ((fps3-fps2)*1000000),
+ (int) ((fps4-fps3)*1000000),
+ (int) ((double_time()-fps4)*1000000));
+# endif
+
+ return delay;
+}
+
+
+// Extracts the C structure that is stored in the jwxyz Java object.
+static struct running_hack *
+getRunningHack (JNIEnv *env, jobject thiz)
+{
+ jlong result = (*env)->GetLongField (env, thiz, runningHackField);
+ struct running_hack *rh = (struct running_hack *)(intptr_t)result;
+ if (rh)
+ rh->jobject = thiz; // update this every time we call into C
+ return rh;
+}
+
+// Look up a class and mark it global in the provided variable.
+static jclass
+acquireClass (JNIEnv *env, const char *className, jobject *globalRef)
+{
+ jclass clazz = (*env)->FindClass(env, className);
+ *globalRef = (*env)->NewGlobalRef(env, clazz);
+ return clazz;
+}
+
+
+/* Note: to find signature strings for native methods:
+ cd ./project/xscreensaver/build/intermediates/classes/debug/
+ javap -s -p org.jwz.xscreensaver.jwxyz
+ */
+
+
+// Implementation of jwxyz's nativeInit Java method.
+//
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *env, jobject thiz,
+ jstring jhack, jobject defaults,
+ jint w, jint h,
+ jobject jni_surface)
+{
+ pthread_mutex_lock(&mutg);
+
+ struct running_hack *rh = calloc(1, sizeof(struct running_hack));
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ // #### simplify
+ if (!classRefCount) {
+ jclass classjwxyz = (*env)->GetObjectClass(env, thiz);
+ globalRefjwxyz = (*env)->NewGlobalRef(env, classjwxyz);
+ runningHackField = (*env)->GetFieldID
+ (env, classjwxyz, "nativeRunningHackPtr", "J");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ jclass classIterable =
+ acquireClass(env, "java/lang/Iterable", &globalRefIterable);
+ iterableIterator = (*env)->GetMethodID
+ (env, classIterable, "iterator", "()Ljava/util/Iterator;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ jclass classIterator =
+ acquireClass(env, "java/util/Iterator", &globalRefIterator);
+ iteratorHasNext = (*env)->GetMethodID
+ (env, classIterator, "hasNext", "()Z");
+ iteratorNext = (*env)->GetMethodID
+ (env, classIterator, "next", "()Ljava/lang/Object;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ jclass classMapEntry =
+ acquireClass(env, "java/util/Map$Entry", &globalRefMapEntry);
+ entryGetKey = (*env)->GetMethodID
+ (env, classMapEntry, "getKey", "()Ljava/lang/Object;");
+ entryGetValue = (*env)->GetMethodID
+ (env, classMapEntry, "getValue", "()Ljava/lang/Object;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+ }
+
+ ++classRefCount;
+
+ // Store the C struct into the Java object.
+ (*env)->SetLongField(env, thiz, runningHackField, (jlong)(intptr_t)rh);
+
+ // TODO: Sort the list so binary search works.
+ const char *hack =(*env)->GetStringUTFChars(env, jhack, NULL);
+
+ int chosen = 0;
+ for (;;) {
+ if (chosen == countof(function_table)) {
+ Log ("Hack not found: %s", hack);
+ abort();
+ }
+ if (!strcmp(function_table[chosen].progname, hack))
+ break;
+ chosen++;
+ }
+
+ (*env)->ReleaseStringUTFChars(env, jhack, hack);
+
+ doinit (thiz, rh, env, &function_table[chosen], defaults, w, h,
+ jni_surface);
+
+ pthread_mutex_unlock(&mutg);
+}
+
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *env, jobject thiz,
+ jint w, jint h, jdouble rot)
+{
+ pthread_mutex_lock(&mutg);
+ if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
+
+ current_rotation = rot;
+
+ Log ("native rotation: %f", current_rotation);
+
+ 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;
+
+ if (ignore_rotation_p(rh->dpy) &&
+ rot != 0 && rot != 180 && rot != -180) {
+ int swap = w;
+ w = h;
+ h = swap;
+ wnd->frame.width = w;
+ wnd->frame.height = h;
+ }
+
+ 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->xsft->visual == GL_VISUAL) {
+ glMatrixMode (GL_PROJECTION);
+ glRotatef (-rot, 0, 0, 1);
+ glMatrixMode (GL_MODELVIEW);
+ }
+
+ END:
+ pthread_mutex_unlock(&mutg);
+}
+
+
+JNIEXPORT jlong JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *env, jobject thiz)
+{
+ pthread_mutex_lock(&mutg);
+ struct running_hack *rh = getRunningHack(env, thiz);
+ jlong result = drawXScreenSaver(env, rh);
+ pthread_mutex_unlock(&mutg);
+ return result;
+}
+
+
+// TODO: Check Java side is calling this properly
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *env, jobject thiz)
+{
+ pthread_mutex_lock(&mutg);
+ if (setjmp (jmp_target)) goto END; // Jump here from jwxyz_abort and return.
+
+ struct running_hack *rh = getRunningHack(env, thiz);
+
+ prepare_context (rh);
+
+ if (rh->initted_p)
+ rh->xsft->free_cb (rh->dpy, rh->window, rh->closure);
+ 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);
+
+ --classRefCount;
+ if (!classRefCount) {
+ (*env)->DeleteGlobalRef(env, globalRefjwxyz);
+ (*env)->DeleteGlobalRef(env, globalRefIterable);
+ (*env)->DeleteGlobalRef(env, globalRefIterator);
+ (*env)->DeleteGlobalRef(env, globalRefMapEntry);
+ }
+
+ END:
+ pthread_mutex_unlock(&mutg);
+}
+
+
+static int
+send_event (struct running_hack *rh, XEvent *e)
+{
+ // Assumes mutex is locked and context is prepared
+
+ int *xP = 0, *yP = 0;
+ switch (e->xany.type) {
+ case ButtonPress: case ButtonRelease:
+ xP = &e->xbutton.x;
+ yP = &e->xbutton.y;
+ break;
+ case MotionNotify:
+ xP = &e->xmotion.x;
+ yP = &e->xmotion.y;
+ break;
+ }
+
+ // Rotate the coordinates in the events to match the pixels.
+ if (xP) {
+ if (ignore_rotation_p (rh->dpy)) {
+ Window win = XRootWindow (rh->dpy, 0);
+ int w = win->frame.width;
+ int h = win->frame.height;
+ int swap;
+ switch ((int) current_rotation) {
+ case 180: case -180: // #### untested
+ *xP = w - *xP;
+ *yP = h - *yP;
+ break;
+ case 90: case -270:
+ swap = *xP; *xP = *yP; *yP = swap;
+ *yP = h - *yP;
+ break;
+ case -90: case 270: // #### untested
+ swap = *xP; *xP = *yP; *yP = swap;
+ *xP = w - *xP;
+ break;
+ }
+ }
+
+ rh->window->window.last_mouse_x = *xP;
+ rh->window->window.last_mouse_y = *yP;
+ }
+
+ return (rh->xsft->event_cb
+ ? rh->xsft->event_cb (rh->dpy, rh->window, rh->closure, e)
+ : 0);
+}
+
+
+static void
+send_queued_events (struct running_hack *rh)
+{
+ struct event_queue *event, *next;
+ if (! rh->event_queue) return;
+ for (event = rh->event_queue, next = event->next;
+ event;
+ event = next, next = (event ? event->next : 0)) {
+ if (! send_event (rh, &event->event)) {
+ // #### flash the screen or something
+ }
+ free (event);
+ }
+ rh->event_queue = 0;
+}
+
+
+static void
+queue_event (JNIEnv *env, jobject thiz, XEvent *e)
+{
+ pthread_mutex_lock (&mutg);
+ struct running_hack *rh = getRunningHack (env, thiz);
+ struct event_queue *q = (struct event_queue *) malloc (sizeof(*q));
+ memcpy (&q->event, e, sizeof(*e));
+ q->next = 0;
+
+ // Put it at the end.
+ struct event_queue *oq;
+ for (oq = rh->event_queue; oq && oq->next; oq = oq->next)
+ ;
+ if (oq)
+ oq->next = q;
+ else
+ rh->event_queue = q;
+
+ pthread_mutex_unlock (&mutg);
+}
+
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *env, jobject thiz,
+ int x, int y, jboolean down)
+{
+ XEvent e;
+ memset (&e, 0, sizeof(e));
+ e.xany.type = (down ? ButtonPress : ButtonRelease);
+ e.xbutton.button = Button1;
+ e.xbutton.x = x;
+ e.xbutton.y = y;
+ queue_event (env, thiz, &e);
+}
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *env, jobject thiz,
+ int x, int y)
+{
+ XEvent e;
+ memset (&e, 0, sizeof(e));
+ e.xany.type = MotionNotify;
+ e.xmotion.x = x;
+ e.xmotion.y = y;
+ queue_event (env, thiz, &e);
+}
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *env, jobject thiz,
+ jboolean down_p,
+ int code, int mods)
+{
+ XEvent e;
+ memset (&e, 0, sizeof(e));
+ e.xkey.keycode = code;
+ e.xkey.state = code;
+ e.xany.type = (down_p ? KeyPress : KeyRelease);
+ queue_event (env, thiz, &e);
+ e.xany.type = KeyRelease;
+ queue_event (env, thiz, &e);
+}
+
+
+/***************************************************************************
+ Backend functions for jwxyz-gl.c
+ */
+
+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)
+{
+ 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)
+{
+ struct running_hack *rh = w->window.rh;
+ JNIEnv *env = w->window.rh->jni_env;
+ if ((*env)->ExceptionOccurred(env)) abort();
+ if (rh->current_drawable != 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;
+ }
+}
+
+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;
+}
+
+
+const XRectangle *
+jwxyz_frame (Drawable d)
+{
+ return &d->frame;
+}
+
+
+unsigned int
+jwxyz_drawable_depth (Drawable d)
+{
+ return (d->type == WINDOW
+ ? visual_depth (NULL, NULL)
+ : d->pixmap.depth);
+}
+
+
+void
+jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
+{
+ xvpos->x = 0;
+ xvpos->y = 0;
+
+ if (xp) {
+ xp->x = w->window.last_mouse_x;
+ xp->y = w->window.last_mouse_y;
+ }
+}
+
+
+static void
+screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure)
+{
+ fps_compute (fpst, 0, -1);
+ fps_draw (fpst);
+}
+
+
+Pixmap
+XCreatePixmap (Display *dpy, Drawable d,
+ unsigned int width, unsigned int height, unsigned int depth)
+{
+ Window win = XRootWindow(dpy, 0);
+
+ Pixmap p = malloc(sizeof(*p));
+ p->type = PIXMAP;
+ p->frame.x = 0;
+ p->frame.y = 0;
+ p->frame.width = width;
+ p->frame.height = height;
+
+ Assert(depth == 1 || depth == visual_depth(NULL, NULL),
+ "XCreatePixmap: bad depth");
+ p->pixmap.depth = depth;
+
+ 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;
+}
+
+
+int
+XFreePixmap (Display *d, Pixmap p)
+{
+ struct running_hack *rh = XRootWindow(d, 0)->window.rh;
+
+ 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;
+}
+
+
+double
+current_device_rotation (void)
+{
+ return current_rotation;
+}
+
+Bool
+ignore_rotation_p (Display *dpy)
+{
+ struct running_hack *rh = XRootWindow(dpy, 0)->window.rh;
+ return rh->ignore_rotation_p;
+}
+
+
+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)
+{
+ JNIEnv *env = window->window.rh->jni_env;
+ jobject obj = window->window.rh->jobject;
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+ jstring jstr = (*env)->NewStringUTF (env, name);
+ jclass c = (*env)->GetObjectClass (env, obj);
+ jmethodID m = (*env)->GetMethodID (env, c, "getStringResource",
+ "(Ljava/lang/String;)Ljava/lang/String;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ jstring jvalue = (m
+ ? (*env)->CallObjectMethod (env, obj, m, jstr)
+ : NULL);
+ (*env)->DeleteLocalRef (env, c);
+ (*env)->DeleteLocalRef (env, jstr);
+ char *ret = 0;
+ 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)
+{
+ Window window = RootWindow (dpy, 0);
+ JNIEnv *env = window->window.rh->jni_env;
+ jobject obj = window->window.rh->jobject;
+
+ jstring jstr = (*env)->NewStringUTF (env, url);
+ jclass c = (*env)->GetObjectClass (env, obj);
+ jmethodID m = (*env)->GetMethodID (env, c, "loadURL",
+ "(Ljava/lang/String;)Ljava/nio/ByteBuffer;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+ jobject buf = (m
+ ? (*env)->CallObjectMethod (env, obj, m, jstr)
+ : NULL);
+ (*env)->DeleteLocalRef (env, c);
+ (*env)->DeleteLocalRef (env, jstr);
+
+ char *body = (char *) (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
+ char *body2;
+ if (body) {
+ int L = (*env)->GetDirectBufferCapacity (env, buf);
+ body2 = malloc (L + 1);
+ memcpy (body2, body, L);
+ body2[L] = 0;
+ } else {
+ body2 = strdup ("ERROR");
+ }
+
+ if (buf)
+ (*env)->DeleteLocalRef (env, buf);
+
+ return body2;
+}
+
+
+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 (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)
+{
+ JNIEnv *env = window->window.rh->jni_env;
+ jobject obj = window->window.rh->jobject;
+
+ 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",
+ "(IILjava/lang/String;IF)[Ljava/lang/Object;");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ jobjectArray array = (m
+ ? (*env)->CallObjectMethod (env, obj, m, (jint)mask_jwxyz,
+ (jint)traits_jwxyz, jname,
+ (jint)font_name_type, (jfloat)size)
+ : NULL);
+
+ (*env)->DeleteLocalRef (env, c);
+
+ if (array) {
+ jobject font = (*env)->GetObjectArrayElement (env, array, 0);
+ 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();
+
+ 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, asc);
+ m = (*env)->GetMethodID (env, c, "floatValue", "()F");
+ if ((*env)->ExceptionOccurred(env)) abort();
+
+ *ascent_ret = (int) (*env)->CallFloatMethod (env, asc, m);
+ *descent_ret = (int) (*env)->CallFloatMethod (env, desc, m);
+
+ return (void *) paint;
+ } else {
+ return 0;
+ }
+}
+
+
+void
+jwxyz_release_native_font (Display *dpy, void *native_font)
+{
+ Window window = RootWindow (dpy, 0);
+ JNIEnv *env = window->window.rh->jni_env;
+ if ((*env)->ExceptionOccurred(env)) abort();
+ (*env)->DeleteGlobalRef (env, (jobject) native_font);
+ if ((*env)->ExceptionOccurred(env)) abort();
+}
+
+
+/* If the local reference table fills up, use this to figure out where
+ you missed a call to DeleteLocalRef. */
+/*
+static void dump_reference_tables(JNIEnv *env)
+{
+ jclass c = (*env)->FindClass(env, "dalvik/system/VMDebug");
+ jmethodID m = (*env)->GetStaticMethodID (env, c, "dumpReferenceTables",
+ "()V");
+ (*env)->CallStaticVoidMethod (env, c, m);
+ (*env)->DeleteLocalRef (env, c);
+}
+*/
+
+
+// Returns the metrics of the multi-character, single-line UTF8 or Latin1
+// string. If pixmap_ret is provided, also renders the text.
+//
+void
+jwxyz_render_text (Display *dpy, void *native_font,
+ const char *str, size_t len, Bool utf8, Bool antialias_p,
+ XCharStruct *cs, char **pixmap_ret)
+{
+ Window window = RootWindow (dpy, 0);
+ JNIEnv *env = window->window.rh->jni_env;
+ jobject obj = window->window.rh->jobject;
+
+ char *s2;
+
+ if (utf8) {
+ s2 = malloc (len + 1);
+ memcpy (s2, str, len);
+ s2[len] = 0;
+ } else { // Convert Latin1 to UTF8
+ s2 = malloc (len * 2 + 1);
+ unsigned char *s3 = (unsigned char *) s2;
+ int i;
+ for (i = 0; i < len; i++) {
+ unsigned char c = ((unsigned char *) str)[i];
+ if (! (c & 0x80)) {
+ *s3++ = c;
+ } else {
+ *s3++ = (0xC0 | (0x03 & (c >> 6)));
+ *s3++ = (0x80 | (0x3F & c));
+ }
+ }
+ *s3 = 0;
+ }
+
+ 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;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),
+ antialias_p)
+ : NULL);
+ (*env)->DeleteLocalRef (env, c);
+ (*env)->DeleteLocalRef (env, jstr);
+ free (s2);
+
+ if ((*env)->ExceptionOccurred(env)) abort();
+ unsigned char *bits = (unsigned char *)
+ (buf ? (*env)->GetDirectBufferAddress (env, buf) : 0);
+ if (bits) {
+ int i = 0;
+ int L = (*env)->GetDirectBufferCapacity (env, buf);
+ if (L < 10) abort();
+ cs->lbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
+ cs->rbearing = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
+ cs->width = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
+ cs->ascent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
+ cs->descent = (bits[i] << 8) | (bits[i+1] & 0xFF); i += 2;
+
+ if (pixmap_ret) {
+ char *pix = malloc (L - i);
+ if (! pix) abort();
+ memcpy (pix, bits + i, L - i);
+ *pixmap_ret = pix;
+ }
+ } else {
+ memset (cs, 0, sizeof(*cs));
+ if (pixmap_ret)
+ *pixmap_ret = 0;
+ }
+
+ if (buf)
+ (*env)->DeleteLocalRef (env, buf);
+}
+
+
+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_draw_random_image (Display *dpy, Drawable drawable, GC gc)
+{
+ Window window = RootWindow (dpy, 0);
+ struct running_hack *rh = window->window.rh;
+ JNIEnv *env = rh->jni_env;
+ jobject obj = rh->jobject;
+
+ Bool images_p =
+ get_boolean_resource (rh->dpy, "chooseRandomImages", "ChooseRandomImages");
+ Bool grab_p =
+ get_boolean_resource (rh->dpy, "grabDesktopImages", "GrabDesktopImages");
+ Bool rotate_p =
+ get_boolean_resource (rh->dpy, "rotateImages", "RotateImages");
+
+ if (!images_p && !grab_p)
+ return 0;
+
+ if (grab_p && images_p) {
+ grab_p = !(random() & 5); /* if both, screenshot 1/5th of the time */
+ images_p = !grab_p;
+ }
+
+ jclass c = (*env)->GetObjectClass (env, obj);
+ jmethodID m = (*env)->GetMethodID (env, c,
+ (grab_p
+ ? "getScreenshot"
+ : "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();
+ (*env)->DeleteLocalRef (env, c);
+
+ if (!img_name) {
+ fprintf (stderr, "failed to load %s\n", (grab_p ? "screenshot" : "image"));
+ return NULL;
+ }
+
+ jobject jbitmap = (*env)->GetObjectArrayElement (env, img_name, 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 */
diff --git a/jwxyz/jwxyz-android.h b/jwxyz/jwxyz-android.h
new file mode 100644
index 0000000..1f5ae52
--- /dev/null
+++ b/jwxyz/jwxyz-android.h
@@ -0,0 +1,121 @@
+/* xscreensaver, Copyright (c) 2016 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+#ifndef __JWXYZ_ANDROID_H__
+#define __JWXYZ_ANDROID_H__
+
+#include "jwxyz.h"
+#include "../hacks/fps.h"
+
+#include <android/log.h>
+/* Native EGL is Android 2.3/API 9. EGL in Java is available from API 1. */
+#include <EGL/egl.h>
+#include <GLES/glext.h>
+#include <jni.h>
+
+/* From utils/visual.c. */
+#define DEFAULT_VISUAL -1
+#define GL_VISUAL -6
+
+struct jwxyz_Drawable {
+ enum { WINDOW, PIXMAP } type;
+ XRectangle frame;
+ union {
+ /* JWXYZ_GL */
+ EGLSurface egl_surface;
+ GLuint texture; /* If this is 0, it's the default framebuffer. */
+
+ /* JWXYZ_IMAGE */
+ void *image_data;
+ };
+ union {
+ struct {
+ struct running_hack *rh;
+ int last_mouse_x, last_mouse_y;
+ } window;
+ struct {
+ int depth;
+ } pixmap;
+ };
+};
+
+struct running_hack {
+ struct xscreensaver_function_table *xsft;
+ Display *dpy;
+ Window window;
+ fps_state *fpst;
+ void *closure;
+ JNIEnv *jni_env;
+ jobject jobject;
+
+ Bool jwxyz_gl_p, egl_p;
+
+ /* JWXYZ_GL */
+ EGLContext egl_ctx;
+ EGLSurface egl_surface;
+ EGLDisplay egl_display;
+ GLint fb_default;
+
+ EGLConfig egl_config;
+
+ struct jwxyz_Drawable frontbuffer;
+ GC copy_gc;
+ Bool gl_fbo_p, frontbuffer_p;
+ GLuint fb_pixmap;
+
+ Drawable current_drawable;
+
+ /* JWXYZ_IMAGE */
+ ANativeWindow *native_window;
+
+ Bool ignore_rotation_p;
+
+ jwzgles_state *gles_state;
+
+ unsigned long frame_count;
+ Bool initted_p;
+ struct event_queue *event_queue;
+};
+
+
+// Methods of the Java class org.jwz.jwxyz that are implemented in C.
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeInit (JNIEnv *, jobject thiz,
+ jstring jhack,
+ jobject defaults,
+ jint w, jint h,
+ jobject jni_surface);
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeResize (JNIEnv *, jobject thiz,
+ jint w, jint h, jdouble rot);
+
+JNIEXPORT jlong JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeRender (JNIEnv *, jobject thiz);
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_nativeDone (JNIEnv *, jobject thiz);
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendButtonEvent (JNIEnv *, jobject thiz,
+ int x, int y, jboolean down);
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendMotionEvent (JNIEnv *, jobject thiz,
+ int x, int y);
+
+JNIEXPORT void JNICALL
+Java_org_jwz_xscreensaver_jwxyz_sendKeyEvent (JNIEnv *, jobject thiz,
+ jboolean down_p,
+ int code, int mods);
+
+#endif // __JWXYZ_ANDROID_H__
diff --git a/jwxyz/jwxyz-cocoa.h b/jwxyz/jwxyz-cocoa.h
new file mode 100644
index 0000000..5f2f61c
--- /dev/null
+++ b/jwxyz/jwxyz-cocoa.h
@@ -0,0 +1,98 @@
+/* xscreensaver, Copyright (c) 1991-2015 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+#ifndef __JWXYZ_COCOA_H__
+#define __JWXYZ_COCOA_H__
+
+#import "XScreenSaverView.h"
+
+#ifdef USE_IPHONE
+# import <UIKit/UIKit.h>
+# define NSView UIView
+# define NSOpenGLContext EAGLContext
+#endif
+
+#ifdef JWXYZ_QUARTZ
+
+struct jwxyz_Drawable {
+ enum { WINDOW, PIXMAP } type;
+ CGContextRef cgc;
+ CGImageRef cgi;
+ XRectangle frame;
+ union {
+ struct {
+ XScreenSaverView *view;
+ int last_mouse_x, last_mouse_y;
+ } window;
+ struct {
+ int depth;
+ void *cgc_buffer; // the bits to which CGContextRef renders
+ } pixmap;
+ };
+};
+
+#elif defined JWXYZ_GL
+
+struct jwxyz_Drawable {
+ enum { WINDOW, PIXMAP } type;
+ /* OS X: Contexts are unique for each pixmap, 'cause life is hectic. (OS X
+ appears to dislike it when you attach different pbuffers to the
+ same context one after the other, apparently.) The Window has this
+ CFRetained because of garbage collection. For both Pixmaps and
+ Windows, CFRelease this when done.
+ iOS: ogl_ctx here is set to either XScreenSaverView.ogl_ctx or
+ XRootWindow()->window.ogl_ctx_pixmap. No garbage collection antics
+ here, so no need to CFRetain anything. Plus, if a screenhack leaks
+ a Pixmap (and they do that all the time), ogl_ctx_pixmap will also
+ get leaked if a Pixmap CFRetains this.
+ */
+ NSOpenGLContext *ogl_ctx; // OpenGL rendering context (OS X)
+# ifdef USE_IPHONE
+ // TODO: Also on OS X as extensions.
+ GLuint gl_framebuffer, gl_renderbuffer;
+# endif // USE_IPHONE
+ CGImageRef cgi;
+ XRectangle frame;
+ union {
+ struct {
+ XScreenSaverView *view;
+ int last_mouse_x, last_mouse_y;
+ struct jwxyz_Drawable *current_drawable;
+# ifndef USE_IPHONE
+ NSOpenGLPixelFormat *pixfmt;
+ GLint virtual_screen;
+# else // USE_IPHONE
+ NSOpenGLContext *ogl_ctx_pixmap;
+# endif
+ } window;
+ struct {
+ int depth;
+# ifndef USE_IPHONE
+ NSOpenGLPixelBuffer *gl_pbuffer;
+ // GLuint blit_texture; // TODO: For blitting from Pbuffers
+# endif
+ } pixmap;
+ };
+};
+
+#endif // JWXYZ_GL
+
+extern NSString *nsstring_from(const char *str, size_t len, int utf8_p);
+
+#ifdef USE_IPHONE
+extern void create_framebuffer (GLuint *gl_framebuffer,
+ GLuint *gl_renderbuffer);
+extern void check_framebuffer_status (void);
+#endif // USE_IPHONE
+
+#define jwxyz_window_view(w) ((w)->window.view)
+
+#endif
diff --git a/jwxyz/jwxyz-cocoa.m b/jwxyz/jwxyz-cocoa.m
new file mode 100644
index 0000000..96d54b8
--- /dev/null
+++ b/jwxyz/jwxyz-cocoa.m
@@ -0,0 +1,1043 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance
+ to the things that Xlib might have done.
+
+ This code is used by both the original jwxyz.m and the new jwxyz-gl.c.
+ */
+
+#import "jwxyzI.h"
+#import "jwxyz-cocoa.h"
+#import "utf8wc.h"
+
+#include <stdarg.h>
+
+#ifdef USE_IPHONE
+# import <OpenGLES/ES1/gl.h>
+# import <OpenGLES/ES1/glext.h>
+# define NSOpenGLContext EAGLContext
+# define NSFont UIFont
+
+# define NSFontTraitMask UIFontDescriptorSymbolicTraits
+// The values for the flags for NSFontTraitMask and
+// UIFontDescriptorSymbolicTraits match up, not that it really matters here.
+# define NSBoldFontMask UIFontDescriptorTraitBold
+# define NSFixedPitchFontMask UIFontDescriptorTraitMonoSpace
+# define NSItalicFontMask UIFontDescriptorTraitItalic
+#endif
+
+#import <CoreText/CTLine.h>
+#import <CoreText/CTRun.h>
+
+#define VTBL JWXYZ_VTBL(dpy)
+
+/* OS X/iOS-specific JWXYZ implementation. */
+
+void
+jwxyz_logv (Bool error, const char *fmt, va_list args)
+{
+ vfprintf (stderr, fmt, args);
+ fputc ('\n', stderr);
+}
+
+/* Instead of calling abort(), throw a real exception, so that
+ XScreenSaverView can catch it and display a dialog.
+ */
+void
+jwxyz_abort (const char *fmt, ...)
+{
+ char s[10240];
+ if (!fmt || !*fmt)
+ strcpy (s, "abort");
+ else
+ {
+ va_list args;
+ va_start (args, fmt);
+ vsprintf (s, fmt, args);
+ va_end (args);
+ }
+ [[NSException exceptionWithName: NSInternalInconsistencyException
+ reason: [NSString stringWithCString: s
+ encoding:NSUTF8StringEncoding]
+ userInfo: nil]
+ raise];
+# undef abort
+ abort(); // not reached
+}
+
+
+const XRectangle *
+jwxyz_frame (Drawable d)
+{
+ return &d->frame;
+}
+
+
+unsigned int
+jwxyz_drawable_depth (Drawable d)
+{
+ return (d->type == WINDOW
+ ? visual_depth (NULL, NULL)
+ : d->pixmap.depth);
+}
+
+
+float
+jwxyz_scale (Window main_window)
+{
+ float scale = 1;
+
+# ifdef USE_IPHONE
+ /* Since iOS screens are physically smaller than desktop screens, scale up
+ the fonts to make them more readable.
+
+ Note that X11 apps on iOS also have the backbuffer sized in points
+ instead of pixels, resulting in an effective X11 screen size of 768x1024
+ or so, even if the display has significantly higher resolution. That is
+ unrelated to this hack, which is really about DPI.
+ */
+ scale = main_window->window.view.hackedContentScaleFactor;
+ if (scale < 1) // iPad Pro magnifies the backbuffer by 3x, which makes text
+ scale = 1; // excessively blurry in BSOD.
+
+# else // !USE_IPHONE
+
+ /* Desktop retina displays also need fonts doubled. */
+ scale = main_window->window.view.hackedContentScaleFactor;
+
+# endif // !USE_IPHONE
+
+ return scale;
+}
+
+
+/* Font metric terminology, as used by X11:
+
+ "lbearing" is the distance from the logical origin to the leftmost pixel.
+ If a character's ink extends to the left of the origin, it is negative.
+
+ "rbearing" is the distance from the logical origin to the rightmost pixel.
+
+ "descent" is the distance from the logical origin to the bottommost pixel.
+ For characters with descenders, it is positive. For superscripts, it
+ is negative.
+
+ "ascent" is the distance from the logical origin to the topmost pixel.
+ It is the number of pixels above the baseline.
+
+ "width" is the distance from the logical origin to the position where
+ the logical origin of the next character should be placed.
+
+ If "rbearing" is greater than "width", then this character overlaps the
+ following character. If smaller, then there is trailing blank space.
+ */
+static void
+utf8_metrics (Display *dpy, NSFont *nsfont, NSString *nsstr, XCharStruct *cs)
+{
+ // Returns the metrics of the multi-character, single-line UTF8 string.
+
+ Drawable d = XRootWindow (dpy, 0);
+
+ CGContextRef cgc = d->cgc;
+ NSDictionary *attr =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ nsfont, NSFontAttributeName,
+ nil];
+ NSAttributedString *astr = [[NSAttributedString alloc]
+ initWithString:nsstr
+ attributes:attr];
+ CTLineRef ctline = CTLineCreateWithAttributedString (
+ (__bridge CFAttributedStringRef) astr);
+ CGContextSetTextPosition (cgc, 0, 0);
+ CGContextSetShouldAntialias (cgc, True); // #### Guess?
+
+ memset (cs, 0, sizeof(*cs));
+
+ // "CTRun represents set of consecutive glyphs sharing the same
+ // attributes and direction".
+ //
+ // We also get multiple runs any time font subsitution happens:
+ // E.g., if the current font is Verdana-Bold, a &larr; character
+ // in the NSString will actually be rendered in LucidaGrande-Bold.
+ //
+ int count = 0;
+ for (id runid in (NSArray *)CTLineGetGlyphRuns(ctline)) {
+ CTRunRef run = (CTRunRef) runid;
+ CFRange r = { 0, };
+ CGRect bbox = CTRunGetImageBounds (run, cgc, r);
+ CGFloat ascent, descent, leading;
+ CGFloat advancement =
+ CTRunGetTypographicBounds (run, r, &ascent, &descent, &leading);
+
+# ifndef USE_IPHONE
+ // Only necessary for when LCD smoothing is enabled, which iOS doesn't do.
+ bbox.origin.x -= 2.0/3.0;
+ bbox.size.width += 4.0/3.0;
+ bbox.size.height += 1.0/2.0;
+# endif
+
+ // Create the metrics for this run:
+ XCharStruct cc;
+ cc.ascent = ceil (bbox.origin.y + bbox.size.height);
+ cc.descent = ceil (-bbox.origin.y);
+ cc.lbearing = floor (bbox.origin.x);
+ cc.rbearing = ceil (bbox.origin.x + bbox.size.width);
+ cc.width = floor (advancement + 0.5);
+
+ // Add those metrics into the cumulative metrics:
+ if (count == 0)
+ *cs = cc;
+ else
+ {
+ cs->ascent = MAX (cs->ascent, cc.ascent);
+ cs->descent = MAX (cs->descent, cc.descent);
+ cs->lbearing = MIN (cs->lbearing, cs->width + cc.lbearing);
+ cs->rbearing = MAX (cs->rbearing, cs->width + cc.rbearing);
+ cs->width = MAX (cs->width, cs->width + cc.width);
+ }
+
+ // Why no y? What about vertical text?
+ // XCharStruct doesn't encapsulate that but XGlyphInfo does.
+
+ count++;
+ }
+
+ [astr release];
+ CFRelease (ctline);
+}
+
+
+static NSArray *
+font_family_members (NSString *family_name)
+{
+# ifndef USE_IPHONE
+ return [[NSFontManager sharedFontManager]
+ availableMembersOfFontFamily:family_name];
+# else
+ return [UIFont fontNamesForFamilyName:family_name];
+# endif
+}
+
+
+const char *
+jwxyz_default_font_family (int require)
+{
+ return require & JWXYZ_STYLE_MONOSPACE ? "Courier" : "Verdana";
+}
+
+
+static NSFontTraitMask
+nsfonttraitmask_for (int font_style)
+{
+ NSFontTraitMask result = 0;
+ if (font_style & JWXYZ_STYLE_BOLD)
+ result |= NSBoldFontMask;
+ if (font_style & JWXYZ_STYLE_ITALIC)
+ result |= NSItalicFontMask;
+ if (font_style & JWXYZ_STYLE_MONOSPACE)
+ result |= NSFixedPitchFontMask;
+ return result;
+}
+
+
+static NSFont *
+try_font (NSFontTraitMask traits, NSFontTraitMask mask,
+ NSString *family_name, float size)
+{
+ NSArray *family_members = font_family_members (family_name);
+ if (!family_members.count) {
+ family_members = font_family_members (
+ [NSString stringWithUTF8String:jwxyz_default_font_family (
+ traits & NSFixedPitchFontMask ? JWXYZ_STYLE_MONOSPACE : 0)]);
+ }
+
+# ifndef USE_IPHONE
+ for (unsigned k = 0; k != family_members.count; ++k) {
+
+ NSArray *member = [family_members objectAtIndex:k];
+ NSFontTraitMask font_mask =
+ [(NSNumber *)[member objectAtIndex:3] unsignedIntValue];
+
+ if ((font_mask & mask) == traits) {
+
+ NSString *name = [member objectAtIndex:0];
+ NSFont *f = [NSFont fontWithName:name size:size];
+ if (!f)
+ break;
+
+ /* Don't use this font if it (probably) doesn't include ASCII characters.
+ */
+ NSStringEncoding enc = [f mostCompatibleStringEncoding];
+ if (! (enc == NSUTF8StringEncoding ||
+ enc == NSISOLatin1StringEncoding ||
+ enc == NSNonLossyASCIIStringEncoding ||
+ enc == NSISOLatin2StringEncoding ||
+ enc == NSUnicodeStringEncoding ||
+ enc == NSWindowsCP1250StringEncoding ||
+ enc == NSWindowsCP1252StringEncoding ||
+ enc == NSMacOSRomanStringEncoding)) {
+ // NSLog(@"skipping \"%@\": encoding = %d", name, enc);
+ break;
+ }
+ // NSLog(@"using \"%@\": %d", name, enc);
+
+ return f;
+ }
+ }
+# else // USE_IPHONE
+
+ // This trick needs iOS 3.1, see "Using SDK-Based Development".
+ Class has_font_descriptor = [UIFontDescriptor class];
+
+ for (NSString *fn in family_members) {
+# define MATCH(X) \
+ ([fn rangeOfString:X options:NSCaseInsensitiveSearch].location \
+ != NSNotFound)
+
+ NSFontTraitMask font_mask;
+ if (has_font_descriptor) {
+ // This only works on iOS 7 and later.
+ font_mask = [[UIFontDescriptor
+ fontDescriptorWithFontAttributes:
+ @{UIFontDescriptorNameAttribute:fn}]
+ symbolicTraits];
+ } else {
+ font_mask = 0;
+ if (MATCH(@"Bold"))
+ font_mask |= NSBoldFontMask;
+ if (MATCH(@"Italic") || MATCH(@"Oblique"))
+ font_mask |= NSItalicFontMask;
+ if (MATCH(@"Courier"))
+ font_mask |= NSFixedPitchFontMask;
+ }
+
+ if ((font_mask & mask) == traits) {
+
+ /* Check if it can do ASCII. No good way to accomplish this!
+ These are fonts present in iPhone Simulator as of June 2012
+ that don't include ASCII.
+ */
+ if (MATCH(@"AppleGothic") || // Korean
+ MATCH(@"Dingbats") || // Dingbats
+ MATCH(@"Emoji") || // Emoticons
+ MATCH(@"Geeza") || // Arabic
+ MATCH(@"Hebrew") || // Hebrew
+ MATCH(@"HiraKaku") || // Japanese
+ MATCH(@"HiraMin") || // Japanese
+ MATCH(@"Kailasa") || // Tibetan
+ MATCH(@"Ornaments") || // Dingbats
+ MATCH(@"STHeiti") // Chinese
+ )
+ break;
+
+ return [UIFont fontWithName:fn size:size];
+ }
+# undef MATCH
+ }
+# endif
+
+ return NULL;
+}
+
+
+/* Returns a random font in the given size and face.
+ */
+static NSFont *
+random_font (NSFontTraitMask traits, NSFontTraitMask mask, float size)
+{
+
+# ifndef USE_IPHONE
+ // Providing Unbold or Unitalic in the mask for availableFontNamesWithTraits
+ // returns an empty list, at least on a system with default fonts only.
+ NSArray *families = [[NSFontManager sharedFontManager]
+ availableFontFamilies];
+ if (!families) return 0;
+# else
+ NSArray *families = [UIFont familyNames];
+
+ // There are many dups in the families array -- uniquify it.
+ {
+ NSArray *sorted_families =
+ [families sortedArrayUsingSelector:@selector(compare:)];
+ NSMutableArray *new_families =
+ [NSMutableArray arrayWithCapacity:sorted_families.count];
+
+ NSString *prev_family = @"";
+ for (NSString *family in sorted_families) {
+ if ([family compare:prev_family])
+ [new_families addObject:family];
+ prev_family = family;
+ }
+
+ families = new_families;
+ }
+# endif // USE_IPHONE
+
+ long n = [families count];
+ if (n <= 0) return 0;
+
+ int j;
+ for (j = 0; j < n; j++) {
+ int i = random() % n;
+ NSString *family_name = [families objectAtIndex:i];
+
+ NSFont *result = try_font (traits, mask, family_name, size);
+ if (result)
+ return result;
+ }
+
+ // None of the fonts support ASCII?
+ return 0;
+}
+
+void *
+jwxyz_load_native_font (Window main_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)
+{
+ NSFont *nsfont = NULL;
+
+ NSFontTraitMask
+ traits = nsfonttraitmask_for (traits_jwxyz),
+ mask = nsfonttraitmask_for (mask_jwxyz);
+
+ NSString *font_name = font_name_type != JWXYZ_FONT_RANDOM ?
+ [[NSString alloc] initWithBytes:font_name_ptr
+ length:font_name_length
+ encoding:NSUTF8StringEncoding] :
+ nil;
+
+ size *= jwxyz_scale (main_window);
+
+ if (font_name_type == JWXYZ_FONT_RANDOM) {
+
+ nsfont = random_font (traits, mask, size);
+ [font_name release];
+
+ } else if (font_name_type == JWXYZ_FONT_FACE) {
+
+ nsfont = [NSFont fontWithName:font_name size:size];
+
+ } else if (font_name_type == JWXYZ_FONT_FAMILY) {
+
+ Assert (size > 0, "zero font size");
+
+ if (!nsfont)
+ nsfont = try_font (traits, mask, font_name, size);
+
+ // if that didn't work, turn off attibutes until it does
+ // (e.g., there is no "Monaco-Bold".)
+ //
+ if (!nsfont && (mask & NSItalicFontMask)) {
+ traits &= ~NSItalicFontMask;
+ mask &= ~NSItalicFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ if (!nsfont && (mask & NSBoldFontMask)) {
+ traits &= ~NSBoldFontMask;
+ mask &= ~NSBoldFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ if (!nsfont && (mask & NSFixedPitchFontMask)) {
+ traits &= ~NSFixedPitchFontMask;
+ mask &= ~NSFixedPitchFontMask;
+ nsfont = try_font (traits, mask, font_name, size);
+ }
+ }
+
+ [font_name release];
+
+ if (nsfont)
+ {
+ if (family_name_ret)
+ *family_name_ret = strdup (nsfont.familyName.UTF8String);
+
+ CFRetain (nsfont); // needed for garbage collection?
+
+ *ascent_ret = ceil ([nsfont ascender]);
+ *descent_ret = -floor ([nsfont descender]);
+
+ Assert([nsfont fontName], "broken NSFont in fid");
+ }
+
+ return nsfont;
+}
+
+
+void
+jwxyz_release_native_font (Display *dpy, void *native_font)
+{
+ // #### DAMMIT! I can't tell what's going wrong here, but I keep getting
+ // crashes in [NSFont ascender] <- query_font, and it seems to go away
+ // if I never release the nsfont. So, fuck it, we'll just leak fonts.
+ // They're probably not very big...
+ //
+ // [fid->nsfont release];
+ // CFRelease (fid->nsfont);
+}
+
+
+// Given a UTF8 string, return an NSString. Bogus UTF8 characters are ignored.
+// We have to do this because stringWithCString returns NULL if there are
+// any invalid characters at all.
+//
+static NSString *
+sanitize_utf8 (const char *in, size_t in_len, Bool *latin1_pP)
+{
+ size_t out_len = in_len * 4; // length of string might increase
+ char *s2 = (char *) malloc (out_len);
+ char *out = s2;
+ const char *in_end = in + in_len;
+ const char *out_end = out + out_len;
+ Bool latin1_p = True;
+
+ while (in < in_end)
+ {
+ unsigned long uc;
+ long L1 = utf8_decode ((const unsigned char *) in, in_end - in, &uc);
+ long L2 = utf8_encode (uc, out, out_end - out);
+ in += L1;
+ out += L2;
+ if (uc > 255) latin1_p = False;
+ }
+ *out = 0;
+ NSString *nsstr =
+ [NSString stringWithCString:s2 encoding:NSUTF8StringEncoding];
+ free (s2);
+ if (latin1_pP) *latin1_pP = latin1_p;
+ return (nsstr ? nsstr : @"");
+}
+
+
+NSString *
+nsstring_from(const char *str, size_t len, int utf8_p)
+{
+ Bool latin1_p;
+ NSString *nsstr = utf8_p ?
+ sanitize_utf8 (str, len, &latin1_p) :
+ [[[NSString alloc] initWithBytes:str
+ length:len
+ encoding:NSISOLatin1StringEncoding]
+ autorelease];
+ return nsstr;
+}
+
+void
+jwxyz_render_text (Display *dpy, void *native_font,
+ const char *str, size_t len, Bool utf8_p, Bool antialias_p,
+ XCharStruct *cs_ret, char **pixmap_ret)
+{
+ utf8_metrics (dpy, (NSFont *)native_font, nsstring_from (str, len, utf8_p),
+ cs_ret);
+
+ Assert (!pixmap_ret, "TODO");
+}
+
+
+void
+jwxyz_get_pos (Window w, XPoint *xvpos, XPoint *xp)
+{
+# ifdef USE_IPHONE
+
+ xvpos->x = 0;
+ xvpos->y = 0;
+
+ if (xp) {
+ xp->x = w->window.last_mouse_x;
+ xp->y = w->window.last_mouse_y;
+ }
+
+# else // !USE_IPHONE
+
+ NSWindow *nsw = [w->window.view window];
+
+ // get bottom left of window on screen, from bottom left
+
+# if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6)
+ NSRect rr1 = [w->window.view convertRect: NSMakeRect(0,0,0,0) toView:nil];
+ NSRect rr2 = [nsw convertRectToScreen: rr1];
+
+ NSPoint wpos = NSMakePoint (rr2.origin.x - rr1.origin.x,
+ rr2.origin.y - rr1.origin.y);
+# else
+ // deprecated as of 10.7
+ NSPoint wpos = [nsw convertBaseToScreen: NSMakePoint(0,0)];
+# endif
+
+
+ // get bottom left of view on window, from bottom left
+ NSPoint vpos;
+ vpos.x = vpos.y = 0;
+ vpos = [w->window.view convertPoint:vpos toView:[nsw contentView]];
+
+ // get bottom left of view on screen, from bottom left
+ vpos.x += wpos.x;
+ vpos.y += wpos.y;
+
+ // get top left of view on screen, from bottom left
+ double s = [w->window.view hackedContentScaleFactor];
+ vpos.y += w->frame.height / s;
+
+ // get top left of view on screen, from top left
+ NSArray *screens = [NSScreen screens];
+ NSScreen *screen = (screens && [screens count] > 0
+ ? [screens objectAtIndex:0]
+ : [NSScreen mainScreen]);
+ NSRect srect = [screen frame];
+ vpos.y = srect.size.height - vpos.y;
+
+ xvpos->x = vpos.x;
+ xvpos->y = vpos.y;
+
+ if (xp) {
+ // get the mouse position on window, from bottom left
+ NSEvent *e = [NSApp currentEvent];
+ NSPoint p = [e locationInWindow];
+
+ // get mouse position on screen, from bottom left
+ p.x += wpos.x;
+ p.y += wpos.y;
+
+ // get mouse position on screen, from top left
+ p.y = srect.size.height - p.y;
+
+ xp->x = (int) p.x;
+ xp->y = (int) p.y;
+ }
+
+# endif // !USE_IPHONE
+}
+
+
+#ifdef USE_IPHONE
+
+void
+check_framebuffer_status (void)
+{
+ int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
+ switch (err) {
+ case GL_FRAMEBUFFER_COMPLETE_OES:
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+ Assert (0, "framebuffer incomplete attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+ Assert (0, "framebuffer incomplete missing attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+ Assert (0, "framebuffer incomplete dimensions");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+ Assert (0, "framebuffer incomplete formats");
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED_OES:
+ Assert (0, "framebuffer unsupported");
+ break;
+/*
+ case GL_FRAMEBUFFER_UNDEFINED:
+ Assert (0, "framebuffer undefined");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ Assert (0, "framebuffer incomplete draw buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ Assert (0, "framebuffer incomplete read buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ Assert (0, "framebuffer incomplete multisample");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
+ Assert (0, "framebuffer incomplete layer targets");
+ break;
+ */
+ default:
+ NSCAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
+ break;
+ }
+}
+
+
+void
+create_framebuffer (GLuint *gl_framebuffer, GLuint *gl_renderbuffer)
+{
+ glGenFramebuffersOES (1, gl_framebuffer);
+ glBindFramebufferOES (GL_FRAMEBUFFER_OES, *gl_framebuffer);
+
+ glGenRenderbuffersOES (1, gl_renderbuffer);
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, *gl_renderbuffer);
+}
+
+#endif // USE_IPHONE
+
+
+#if defined JWXYZ_QUARTZ
+
+/* Pushes a GC context; sets Fill and Stroke colors to the background color.
+ */
+static void
+push_bg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ push_color_gc (dpy, d, gc, gcv->background, gcv->antialias_p, fill_p);
+}
+
+
+void
+jwxyz_quartz_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)
+{
+ XRectangle src_frame = src->frame, dst_frame = dst->frame;
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+
+ BOOL mask_p = src->type == PIXMAP && src->pixmap.depth == 1;
+
+
+ /* If we're copying from a bitmap to a bitmap, and there's nothing funny
+ going on with clipping masks or depths or anything, optimize it by
+ just doing a memcpy instead of going through a CGI.
+ */
+ if (gcv->function == GXcopy &&
+ !gcv->clip_mask &&
+ jwxyz_drawable_depth (src) == jwxyz_drawable_depth (dst)) {
+
+ Assert(!src_frame.x &&
+ !src_frame.y &&
+ !dst_frame.x &&
+ !dst_frame.y,
+ "unexpected non-zero origin");
+
+ jwxyz_blit (CGBitmapContextGetData (src->cgc),
+ CGBitmapContextGetBytesPerRow (src->cgc), src_x, src_y,
+ CGBitmapContextGetData (dst->cgc),
+ CGBitmapContextGetBytesPerRow (dst->cgc), dst_x, dst_y,
+ width, height);
+
+ } else {
+ CGRect
+ src_rect = CGRectMake(src_x, src_y, width, height),
+ dst_rect = CGRectMake(dst_x, dst_y, width, height);
+
+ src_rect.origin = map_point (src, src_rect.origin.x,
+ src_rect.origin.y + src_rect.size.height);
+ dst_rect.origin = map_point (dst, dst_rect.origin.x,
+ dst_rect.origin.y + src_rect.size.height);
+
+ NSObject *releaseme = 0;
+ CGImageRef cgi;
+ BOOL free_cgi_p = NO;
+
+ // We must first copy the bits to an intermediary CGImage object, then
+ // copy that to the destination drawable's CGContext.
+ //
+ // First we get a CGImage out of the pixmap CGContext -- it's the whole
+ // pixmap, but it presumably shares the data pointer instead of copying
+ // it. We then cache that CGImage it inside the Pixmap object. Note:
+ // invalidate_drawable_cache() must be called to discard this any time a
+ // modification is made to the pixmap, or we'll end up re-using old bits.
+ //
+ if (!src->cgi)
+ src->cgi = CGBitmapContextCreateImage (src->cgc);
+ cgi = src->cgi;
+
+ // if doing a sub-rect, trim it down.
+ if (src_rect.origin.x != src_frame.x ||
+ src_rect.origin.y != src_frame.y ||
+ src_rect.size.width != src_frame.width ||
+ src_rect.size.height != src_frame.height) {
+ // #### I don't understand why this is needed...
+ src_rect.origin.y = (src_frame.height -
+ src_rect.size.height - src_rect.origin.y);
+ // This does not copy image data, so it should be fast.
+ cgi = CGImageCreateWithImageInRect (cgi, src_rect);
+ free_cgi_p = YES;
+ }
+
+ CGContextRef cgc = dst->cgc;
+
+ if (mask_p) { // src depth == 1
+
+ push_bg_gc (dpy, dst, gc, YES);
+
+ // fill the destination rectangle with solid background...
+ CGContextFillRect (cgc, dst_rect);
+
+ Assert (cgc, "no CGC with 1-bit XCopyArea");
+
+ // then fill in a solid rectangle of the fg color, using the image as an
+ // alpha mask. (the image has only values of BlackPixel or WhitePixel.)
+ set_color (dpy, cgc, gcv->foreground, VTBL->gc_depth (gc),
+ gcv->alpha_allowed_p, YES);
+ CGContextClipToMask (cgc, dst_rect, cgi);
+ CGContextFillRect (cgc, dst_rect);
+
+ pop_gc (dst, gc);
+
+ } else { // src depth > 1
+
+ push_gc (dst, gc);
+
+ // copy the CGImage onto the destination CGContext
+ //Assert(CGImageGetColorSpace(cgi) == dpy->colorspace, "bad colorspace");
+ CGContextDrawImage (cgc, dst_rect, cgi);
+
+ pop_gc (dst, gc);
+ }
+
+ if (free_cgi_p) CGImageRelease (cgi);
+
+ if (releaseme) [releaseme release];
+ }
+
+ invalidate_drawable_cache (dst);
+}
+
+#elif defined JWXYZ_GL
+
+/* Warning! The JWXYZ_GL code here is experimental and provisional and not at
+ all ready for prime time. Please be careful.
+ */
+
+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)
+{
+ /* TODO:
+ - glCopyPixels if src == dst
+ - Pixel buffer copying
+ - APPLE_framebuffer_multisample has ResolveMultisampleFramebufferAPPLE,
+ which is like a blit.
+ */
+
+ /* Strange and ugly flickering when going the glCopyTexImage2D route on
+ OS X. (Early 2009 Mac mini, OS X 10.10)
+ */
+# ifdef USE_IPHONE
+
+ /* TODO: This might not still work. */
+ jwxyz_bind_drawable (dpy, dpy->main_window, src);
+ jwxyz_gl_copy_area_read_tex_image (dpy, jwxyz_frame (src)->height,
+ src_x, src_y, width, height, dst_x, dst_y);
+ jwxyz_bind_drawable (dpy, dpy->main_window, dst);
+ jwxyz_gl_copy_area_write_tex_image (dpy, gc, src_x, src_y,
+ width, height, dst_x, dst_y);
+# else // !USE_IPHONE
+ jwxyz_gl_copy_area_read_pixels (dpy, src, dst, gc,
+ src_x, src_y, width, height, dst_x, dst_y);
+# endif // !USE_IPHONE
+ jwxyz_assert_gl ();
+}
+
+
+void
+jwxyz_assert_gl ()
+{
+ // This is like check_gl_error, except this happens for debug builds only.
+#ifndef __OPTIMIZE__
+ if([NSOpenGLContext currentContext])
+ {
+ // glFinish here drops FPS into the toilet. It might need to be on if
+ // something goes wrong.
+ // glFinish();
+ GLenum error = glGetError();
+ Assert (!error, "jwxyz_assert_gl: OpenGL error");
+ }
+#endif // !__OPTIMIZE__
+}
+
+void
+jwxyz_assert_drawable (Window main_window, Drawable d)
+{
+#if !defined USE_IPHONE && !defined __OPTIMIZE__
+ XScreenSaverView *view = main_window->window.view;
+ NSOpenGLContext *ogl_ctx = [view oglContext];
+
+ if (d->type == WINDOW) {
+ Assert([ogl_ctx view] == view,
+ "jwxyz_assert_display: ogl_ctx view not set!");
+ }
+
+ @try {
+ /* Assert([d->ctx isKindOfClass:[NSOpenGLContext class]], "Not a context."); */
+ Class c = [ogl_ctx class];
+ Assert([c isSubclassOfClass:[NSOpenGLContext class]], "Not a context.");
+ // [d->ctx makeCurrentContext];
+ }
+ @catch (NSException *exception) {
+ perror([[exception reason] UTF8String]);
+ jwxyz_abort();
+ }
+#endif // !USE_IPHONE && !__OPTIMIZE__
+}
+
+
+void
+jwxyz_bind_drawable (Window main_window, Drawable d)
+{
+ /* Windows and Pixmaps need to use different contexts with OpenGL
+ screenhacks, because an OpenGL screenhack sets state in an arbitrary
+ fashion, but jwxyz-gl.c has its own ideas w.r.t. OpenGL state.
+
+ On iOS, all pixmaps can use the same context with different FBOs. Which
+ is nice.
+ */
+
+ /* OpenGL screenhacks in general won't be drawing on the Window, but they
+ can and will draw on a Pixmap -- but an OpenGL call following an Xlib
+ call won't be able to fix the fact that it's drawing offscreen.
+ */
+
+ /* EXT_direct_state_access might be appropriate, but it's apparently not
+ available on Mac OS X.
+ */
+
+ // jwxyz_assert_display (dpy);
+ jwxyz_assert_drawable (main_window, main_window);
+ jwxyz_assert_gl ();
+ jwxyz_assert_drawable (main_window, d);
+
+#if defined USE_IPHONE && !defined __OPTIMIZE__
+ Drawable current_drawable = main_window->window.current_drawable;
+ Assert (!current_drawable
+ || current_drawable->ogl_ctx == [EAGLContext currentContext],
+ "bind_drawable: Wrong context.");
+ if (current_drawable) {
+ GLint framebuffer;
+ glGetIntegerv (GL_FRAMEBUFFER_BINDING_OES, &framebuffer);
+ Assert (framebuffer == current_drawable->gl_framebuffer,
+ "bind_drawable: Wrong framebuffer.");
+ }
+#endif
+
+ if (main_window->window.current_drawable != d) {
+ main_window->window.current_drawable = d;
+
+ /* Doing this repeatedly is probably not OK performance-wise. Probably. */
+#ifndef USE_IPHONE
+ [d->ogl_ctx makeCurrentContext];
+#else
+ [EAGLContext setCurrentContext:d->ogl_ctx];
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, d->gl_framebuffer);
+ if (d->type == PIXMAP) {
+ glViewport (0, 0, d->frame.width, d->frame.height);
+ jwxyz_set_matrices (d->frame.width, d->frame.height);
+ }
+#endif
+ }
+
+ jwxyz_assert_gl ();
+}
+
+
+Pixmap
+XCreatePixmap (Display *dpy, Drawable d,
+ unsigned int width, unsigned int height, unsigned int depth)
+{
+ Pixmap p = (Pixmap) calloc (1, sizeof(*p));
+ p->type = PIXMAP;
+ p->frame.width = width;
+ p->frame.height = height;
+ p->pixmap.depth = depth;
+
+ Assert (depth == 1 || depth == 32, "XCreatePixmap: depth must be 32");
+
+ /* TODO: If Pixel buffers are not supported, do something about it. */
+ Window w = XRootWindow (dpy, 0);
+
+# ifndef USE_IPHONE
+
+ p->ogl_ctx = [[NSOpenGLContext alloc]
+ initWithFormat:w->window.pixfmt
+ shareContext:w->ogl_ctx];
+ CFRetain (p->ogl_ctx);
+
+ [p->ogl_ctx makeCurrentContext]; // This is indeed necessary.
+
+ p->pixmap.gl_pbuffer = [[NSOpenGLPixelBuffer alloc]
+ /* TODO: Only if there are rectangluar textures. */
+ initWithTextureTarget:GL_TEXTURE_RECTANGLE_EXT
+ /* TODO: Make sure GL_RGBA isn't better. */
+ textureInternalFormat:GL_RGB
+ textureMaxMipMapLevel:0
+ pixelsWide:width
+ pixelsHigh:height];
+ CFRetain (p->pixmap.gl_pbuffer);
+
+ [p->ogl_ctx setPixelBuffer:p->pixmap.gl_pbuffer
+ cubeMapFace:0
+ mipMapLevel:0
+ currentVirtualScreen:w->window.virtual_screen];
+
+# else // USE_IPHONE
+
+ p->ogl_ctx = w->window.ogl_ctx_pixmap;
+
+ [EAGLContext setCurrentContext:p->ogl_ctx];
+ create_framebuffer (&p->gl_framebuffer, &p->gl_renderbuffer);
+
+ glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, width, height);
+ glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
+ GL_RENDERBUFFER_OES, p->gl_renderbuffer);
+
+ check_framebuffer_status ();
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, p->gl_framebuffer);
+
+# endif // USE_IPHONE
+
+ w->window.current_drawable = p;
+ glViewport (0, 0, width, height);
+ jwxyz_set_matrices (width, height);
+
+# ifndef __OPTIMIZE__
+ glClearColor (frand(1), frand(1), frand(1), 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+# endif
+
+ return p;
+}
+
+int
+XFreePixmap (Display *d, Pixmap p)
+{
+ Assert (p && p->type == PIXMAP, "not a pixmap");
+
+ Window w = RootWindow (d, 0);
+
+# ifndef USE_IPHONE
+ CFRelease (p->ogl_ctx);
+ [p->ogl_ctx release];
+
+ CFRelease (p->pixmap.gl_pbuffer);
+ [p->pixmap.gl_pbuffer release];
+# else // USE_IPHONE
+ glDeleteRenderbuffersOES (1, &p->gl_renderbuffer);
+ glDeleteFramebuffersOES (1, &p->gl_framebuffer);
+# endif // USE_IPHONE
+
+ if (w->window.current_drawable == p) {
+ w->window.current_drawable = NULL;
+ }
+
+ free (p);
+ return 0;
+}
+
+#endif // JWXYZ_GL
diff --git a/jwxyz/jwxyz-common.c b/jwxyz/jwxyz-common.c
new file mode 100644
index 0000000..1a31ae3
--- /dev/null
+++ b/jwxyz/jwxyz-common.c
@@ -0,0 +1,1828 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance
+ to the things that Xlib might have done.
+
+ This is the version of jwxyz for Android. The version used by MacOS
+ and iOS is in jwxyz.m.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef HAVE_JWXYZ /* whole file */
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "jwxyzI.h"
+#include "pow2.h"
+#include "utf8wc.h"
+#include "xft.h"
+
+/* There's only one Window for a given jwxyz_Display. */
+#define assert_window(dpy, w) \
+ Assert (w == RootWindow (dpy, 0), "not a window")
+
+#define VTBL JWXYZ_VTBL(dpy)
+
+struct jwxyz_Font {
+ Display *dpy;
+ void *native_font;
+ int refcount; // for deciding when to release the native font
+ int ascent, descent;
+ char *xa_font;
+
+ // In X11, "Font" is just an ID, and "XFontStruct" contains the metrics.
+ // But we need the metrics on both of them, so they go here.
+ XFontStruct metrics;
+};
+
+struct jwxyz_XFontSet {
+ XFontStruct *font;
+};
+
+
+void
+Log (const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+ Logv (fmt, args);
+ va_end (args);
+}
+
+
+int
+XDisplayWidth (Display *dpy, int screen)
+{
+ return jwxyz_frame (XRootWindow (dpy, 0))->width;
+}
+
+int
+XDisplayHeight (Display *dpy, int screen)
+{
+ return jwxyz_frame (XRootWindow (dpy, 0))->height;
+}
+
+
+/* XLFDs use dots per inch, but Xlib uses millimeters. Go figure. */
+static int
+size_mm (Display *dpy, unsigned size)
+{
+ /* ((mm / inch) / (points / inch)) * dots / (dots / points) */
+ return (25.4 / 72) * size / jwxyz_scale (XRootWindow (dpy,0)) + 0.5;
+}
+
+int
+XDisplayWidthMM (Display *dpy, int screen)
+{
+ return size_mm (dpy, XDisplayWidth (dpy, screen));
+}
+
+int
+XDisplayHeightMM (Display *dpy, int screen)
+{
+ return size_mm (dpy, XDisplayHeight (dpy, screen));
+}
+
+unsigned long
+XBlackPixelOfScreen(Screen *screen)
+{
+ return DefaultVisualOfScreen (screen)->alpha_mask;
+}
+
+unsigned long
+XWhitePixelOfScreen(Screen *screen)
+{
+ Visual *v = DefaultVisualOfScreen (screen);
+ return (v->red_mask | v->green_mask |v->blue_mask | v->alpha_mask);
+}
+
+unsigned long
+XCellsOfScreen(Screen *screen)
+{
+ Visual *v = DefaultVisualOfScreen (screen);
+ return (v->red_mask | v->green_mask |v->blue_mask);
+}
+
+void
+jwxyz_validate_pixel (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p)
+{
+ Assert (depth == 1 || depth == visual_depth(NULL, NULL),
+ "invalid depth: %d", depth);
+
+ if (depth == 1)
+ Assert ((pixel == 0 || pixel == 1), "bogus mono pixel: 0x%08X", pixel);
+ else if (!alpha_allowed_p)
+ Assert (((pixel & BlackPixel(dpy,0)) == BlackPixel(dpy,0)),
+ "bogus color pixel: 0x%08X", pixel);
+}
+
+
+int
+XDrawPoint (Display *dpy, Drawable d, GC gc, int x, int y)
+{
+ XPoint p;
+ p.x = x;
+ p.y = y;
+ return XDrawPoints (dpy, d, gc, &p, 1, CoordModeOrigin);
+}
+
+
+Bool
+jwxyz_dumb_drawing_mode(Display *dpy, Drawable d, GC gc,
+ int x, int y, unsigned width, unsigned height)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+
+ if (gcv->function == GXset || gcv->function == GXclear) {
+ // "set" and "clear" are dumb drawing modes that ignore the source
+ // bits and just draw solid rectangles.
+ unsigned depth = VTBL->gc_depth (gc);
+ jwxyz_fill_rect (dpy, d, 0, x, y, width, height,
+ (gcv->function == GXset
+ ? (depth == 1 ? 1 : WhitePixel(dpy,0))
+ : (depth == 1 ? 0 : BlackPixel(dpy,0))));
+ return True;
+ }
+
+ return False;
+}
+
+
+int
+XCopyArea (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)
+{
+ Assert (gc, "no GC");
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dst_x < 65535 && dst_x > -65535), "improbably large dst_x");
+ Assert ((dst_y < 65535 && dst_y > -65535), "improbably large dst_y");
+
+ if (width == 0 || height == 0)
+ return 0;
+
+ if (jwxyz_dumb_drawing_mode (dpy, dst, gc, dst_x, dst_y, width, height))
+ return 0;
+
+ XRectangle src_frame, dst_frame; // Sizes and origins of the two drawables
+ Bool clipped = False; // Whether we did any clipping of the rects.
+
+ src_frame = *jwxyz_frame (src);
+ dst_frame = *jwxyz_frame (dst);
+
+ // Initialize src_rect...
+ //
+ src_x += src_frame.x;
+ src_y += src_frame.y;
+ if (src_y < -65535) Assert(0, "src.origin.y went nuts");
+
+ // Initialize dst_rect...
+ //
+ dst_x += dst_frame.x;
+ dst_y += dst_frame.y;
+ if (dst_y < -65535) Assert(0, "dst.origin.y went nuts");
+
+ // Use signed width and height for this...
+ int width0 = width, height0 = height;
+
+ // Clip rects to frames...
+ //
+
+# define CLIP(THIS,THAT,VAL,SIZE) do { \
+ int off = THIS##_##VAL; \
+ if (off < 0) { \
+ clipped = True; \
+ SIZE##0 += off; \
+ THIS##_##VAL -= off; \
+ THAT##_##VAL -= off; \
+ } \
+ off = (( THIS##_##VAL + SIZE##0) - \
+ (THIS##_frame.VAL + THIS##_frame.SIZE)); \
+ if (off > 0) { \
+ clipped = True; \
+ SIZE##0 -= off; \
+ }} while(0)
+
+ CLIP (dst, src, x, width);
+ CLIP (dst, src, y, height);
+
+ // Not actually the original dst_rect, just the one before it's clipped to
+ // the src_frame.
+ int orig_dst_x = dst_x;
+ int orig_dst_y = dst_y;
+ int orig_width = width0;
+ int orig_height = height0;
+
+ if (width0 <= 0 || height0 <= 0)
+ return 0;
+
+ CLIP (src, dst, x, width);
+ CLIP (src, dst, y, height);
+# undef CLIP
+
+ // Sort-of-special case where no pixels can be grabbed from the source,
+ // and the whole destination is filled with the background color.
+ if (width0 <= 0 || height0 <= 0) {
+ width0 = 0;
+ height0 = 0;
+ } else {
+ VTBL->copy_area (dpy, src, dst, gc,
+ src_x, src_y, width0, height0, dst_x, dst_y);
+ }
+
+ // If either the src or dst rects did not lie within their drawables, then
+ // we have adjusted both the src and dst rects to account for the clipping;
+ // that means we need to clear to the background, so that clipped bits end
+ // up in the bg color instead of simply not being copied.
+ //
+ // This has to happen after the copy, because if it happens before, the
+ // cleared area will get grabbed if it overlaps with the source rectangle.
+ //
+ if (clipped && dst == XRootWindow (dpy,0)) {
+ int dst_x0 = dst_x;
+ int dst_y0 = dst_y;
+
+ Assert (orig_dst_x >= 0 &&
+ orig_dst_x + orig_width <= dst_frame.width &&
+ orig_dst_y >= 0 &&
+ orig_dst_y + orig_height <= dst_frame.height,
+ "wrong dimensions");
+
+ XRectangle rects[4];
+ XRectangle *rects_end = rects;
+
+ if (orig_dst_y < dst_y0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = orig_dst_y;
+ rects_end->width = orig_width;
+ rects_end->height = dst_y0 - orig_dst_y;
+ ++rects_end;
+ }
+
+ if (orig_dst_y + orig_height > dst_y0 + height0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = dst_y0 + height0;
+ rects_end->width = orig_width;
+ rects_end->height = orig_dst_y + orig_height - dst_y0 - height0;
+ ++rects_end;
+ }
+
+ if (orig_dst_x < dst_x0) {
+ rects_end->x = orig_dst_x;
+ rects_end->y = dst_y0;
+ rects_end->width = dst_x0 - orig_dst_x;
+ rects_end->height = height0;
+ ++rects_end;
+ }
+
+ if (dst_x0 + width0 < orig_dst_x + orig_width) {
+ rects_end->x = dst_x0 + width0;
+ rects_end->y = dst_y0;
+ rects_end->width = orig_dst_x + orig_width - dst_x0 - width0;
+ rects_end->height = height0;
+ ++rects_end;
+ }
+
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ int old_function = gcv->function;
+ gcv->function = GXcopy;
+ VTBL->fill_rects (dpy, dst, gc, rects, rects_end - rects,
+ *VTBL->window_background (dpy));
+ gcv->function = old_function;
+ }
+
+ return 0;
+}
+
+
+void
+jwxyz_blit (const void *src_data, ptrdiff_t src_pitch,
+ unsigned src_x, unsigned src_y,
+ void *dst_data, ptrdiff_t dst_pitch,
+ unsigned dst_x, unsigned dst_y,
+ unsigned width, unsigned height)
+{
+ Bool same = src_data == dst_data;
+ src_data = SEEK_XY (src_data, src_pitch, src_x, src_y);
+ dst_data = SEEK_XY (dst_data, dst_pitch, dst_x, dst_y);
+
+ size_t bytes = width * 4;
+
+ if (same && dst_y > src_y) {
+ // Copy upwards if the areas might overlap.
+ src_data += src_pitch * (height - 1);
+ dst_data += dst_pitch * (height - 1);
+ src_pitch = -src_pitch;
+ dst_pitch = -dst_pitch;
+ }
+
+ while (height) {
+ // memcpy is an alias for memmove on macOS.
+ memmove (dst_data, src_data, bytes);
+ src_data += src_pitch;
+ dst_data += dst_pitch;
+ --height;
+ }
+}
+
+
+int
+XCopyPlane (Display *dpy, Drawable src, Drawable dest, GC gc,
+ int src_x, int src_y,
+ unsigned width, int height,
+ int dest_x, int dest_y, unsigned long plane)
+{
+ Assert ((VTBL->gc_depth (gc) == 1 || plane == 1), "hairy plane mask!");
+
+ // This isn't right: XCopyPlane() is supposed to map 1/0 to fg/bg,
+ // not to white/black.
+ return XCopyArea (dpy, src, dest, gc,
+ src_x, src_y, width, height, dest_x, dest_y);
+}
+
+
+int
+XDrawLine (Display *dpy, Drawable d, GC gc, int x1, int y1, int x2, int y2)
+{
+ XSegment segment;
+ segment.x1 = x1;
+ segment.y1 = y1;
+ segment.x2 = x2;
+ segment.y2 = y2;
+ XDrawSegments (dpy, d, gc, &segment, 1);
+ return 0;
+}
+
+
+int
+XSetWindowBackground (Display *dpy, Window w, unsigned long pixel)
+{
+ Assert (w == XRootWindow (dpy,0), "not a window");
+ jwxyz_validate_pixel (dpy, pixel, visual_depth (NULL, NULL), False);
+ *VTBL->window_background (dpy) = pixel;
+ return 0;
+}
+
+void
+jwxyz_fill_rect (Display *dpy, Drawable d, GC gc,
+ int x, int y, unsigned int width, unsigned int height,
+ unsigned long pixel)
+{
+ XRectangle r = {x, y, width, height};
+ VTBL->fill_rects (dpy, d, gc, &r, 1, pixel);
+}
+
+int
+XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height)
+{
+ jwxyz_fill_rect (dpy, d, gc, x, y, width, height,
+ VTBL->gc_gcv (gc)->foreground);
+ return 0;
+}
+
+int
+XDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height)
+{
+ XPoint points[5] = {
+ {x, y},
+ {x, y + height},
+ {x + width, y + height},
+ {x + width, y},
+ {x, y}
+ };
+
+ XDrawLines(dpy, d, gc, points, 5, CoordModeOrigin);
+ return 0;
+}
+
+int
+XFillRectangles (Display *dpy, Drawable d, GC gc, XRectangle *rects, int n)
+{
+ VTBL->fill_rects (dpy, d, gc, rects, n, VTBL->gc_gcv (gc)->foreground);
+ return 0;
+}
+
+
+int
+XClearArea (Display *dpy, Window win, int x, int y, int w, int h, Bool exp)
+{
+ Assert(win == XRootWindow(dpy,0), "XClearArea: not a window");
+ Assert(!exp, "XClearArea: exposures unsupported");
+ jwxyz_fill_rect (dpy, win, 0, x, y, w, h, *VTBL->window_background (dpy));
+ return 0;
+}
+
+
+int
+XDrawArc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height, int angle1, int angle2)
+{
+ return VTBL->draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2,
+ False);
+}
+
+int
+XFillArc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height, int angle1, int angle2)
+{
+ return VTBL->draw_arc (dpy, d, gc, x, y, width, height, angle1, angle2,
+ True);
+}
+
+int
+XDrawArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs)
+{
+ int i;
+ for (i = 0; i < narcs; i++)
+ VTBL->draw_arc (dpy, d, gc,
+ arcs[i].x, arcs[i].y,
+ arcs[i].width, arcs[i].height,
+ arcs[i].angle1, arcs[i].angle2,
+ False);
+ return 0;
+}
+
+int
+XFillArcs (Display *dpy, Drawable d, GC gc, XArc *arcs, int narcs)
+{
+ int i;
+ for (i = 0; i < narcs; i++)
+ VTBL->draw_arc (dpy, d, gc,
+ arcs[i].x, arcs[i].y,
+ arcs[i].width, arcs[i].height,
+ arcs[i].angle1, arcs[i].angle2,
+ True);
+ return 0;
+}
+
+void
+jwxyz_gcv_defaults (Display *dpy, XGCValues *gcv, int depth)
+{
+ memset (gcv, 0, sizeof(*gcv));
+ gcv->function = GXcopy;
+ gcv->foreground = (depth == 1 ? 1 : WhitePixel(dpy,0));
+ gcv->background = (depth == 1 ? 0 : BlackPixel(dpy,0));
+ gcv->line_width = 1;
+ gcv->cap_style = CapButt;
+ gcv->join_style = JoinMiter;
+ gcv->fill_rule = EvenOddRule;
+
+ gcv->alpha_allowed_p = False;
+ gcv->antialias_p = True;
+}
+
+
+int
+XChangeGC (Display *dpy, GC gc, unsigned long mask, XGCValues *from)
+{
+ if (! mask) return 0;
+ Assert (gc && from, "no gc");
+ if (!gc || !from) return 0;
+
+ XGCValues *to = VTBL->gc_gcv (gc);
+ unsigned depth = VTBL->gc_depth (gc);
+
+ if (mask & GCFunction) to->function = from->function;
+ if (mask & GCForeground) to->foreground = from->foreground;
+ if (mask & GCBackground) to->background = from->background;
+ if (mask & GCLineWidth) to->line_width = from->line_width;
+ if (mask & GCCapStyle) to->cap_style = from->cap_style;
+ if (mask & GCJoinStyle) to->join_style = from->join_style;
+ if (mask & GCFillRule) to->fill_rule = from->fill_rule;
+ if (mask & GCClipXOrigin) to->clip_x_origin = from->clip_x_origin;
+ if (mask & GCClipYOrigin) to->clip_y_origin = from->clip_y_origin;
+ if (mask & GCSubwindowMode) to->subwindow_mode = from->subwindow_mode;
+
+ if (mask & GCClipMask) XSetClipMask (dpy, gc, from->clip_mask);
+ if (mask & GCFont) XSetFont (dpy, gc, from->font);
+
+ if (mask & GCForeground)
+ jwxyz_validate_pixel (dpy, from->foreground, depth, to->alpha_allowed_p);
+ if (mask & GCBackground)
+ jwxyz_validate_pixel (dpy, from->background, depth, to->alpha_allowed_p);
+
+ Assert ((! (mask & (GCLineStyle |
+ GCPlaneMask |
+ GCFillStyle |
+ GCTile |
+ GCStipple |
+ GCTileStipXOrigin |
+ GCTileStipYOrigin |
+ GCGraphicsExposures |
+ GCDashOffset |
+ GCDashList |
+ GCArcMode))),
+ "unimplemented gcvalues mask");
+
+ return 0;
+}
+
+
+Status
+XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
+{
+ assert_window(dpy, w);
+ memset (xgwa, 0, sizeof(*xgwa));
+ const XRectangle *frame = jwxyz_frame (w);
+ xgwa->x = frame->x;
+ xgwa->y = frame->y;
+ xgwa->width = frame->width;
+ xgwa->height = frame->height;
+ xgwa->depth = visual_depth (NULL, NULL);
+ xgwa->screen = DefaultScreenOfDisplay (dpy);
+ xgwa->visual = XDefaultVisualOfScreen (xgwa->screen);
+ return 0;
+}
+
+Status
+XGetGeometry (Display *dpy, Drawable d, Window *root_ret,
+ int *x_ret, int *y_ret,
+ unsigned int *w_ret, unsigned int *h_ret,
+ unsigned int *bw_ret, unsigned int *d_ret)
+{
+ const XRectangle *frame = jwxyz_frame (d);
+ *x_ret = frame->x;
+ *y_ret = frame->y;
+ *w_ret = frame->width;
+ *h_ret = frame->height;
+ *d_ret = jwxyz_drawable_depth (d);
+ *root_ret = RootWindow (dpy, 0);
+ *bw_ret = 0;
+ return True;
+}
+
+
+Status
+XAllocColor (Display *dpy, Colormap cmap, XColor *color)
+{
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay (dpy));
+ color->pixel =
+ (((color->red << 16) >> (31 - i_log2(v->red_mask))) & v->red_mask) |
+ (((color->green << 16) >> (31 - i_log2(v->green_mask))) & v->green_mask) |
+ (((color->blue << 16) >> (31 - i_log2(v->blue_mask))) & v->blue_mask) |
+ v->alpha_mask;
+ return 1;
+}
+
+Status
+XAllocColorCells (Display *dpy, Colormap cmap, Bool contig,
+ unsigned long *pmret, unsigned int npl,
+ unsigned long *pxret, unsigned int npx)
+{
+ return 0;
+}
+
+int
+XStoreColors (Display *dpy, Colormap cmap, XColor *colors, int n)
+{
+ Assert(0, "XStoreColors called");
+ return 0;
+}
+
+int
+XStoreColor (Display *dpy, Colormap cmap, XColor *c)
+{
+ Assert(0, "XStoreColor called");
+ return 0;
+}
+
+int
+XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int npixels,
+ unsigned long planes)
+{
+ return 0;
+}
+
+Status
+XParseColor (Display *dpy, Colormap cmap, const char *spec, XColor *ret)
+{
+ unsigned char r=0, g=0, b=0;
+ if (*spec == '#' && strlen(spec) == 7) {
+ static unsigned const char hex[] = { // yeah yeah, shoot me.
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
+ 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,10,11,12,13,14,15,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ const unsigned char *uspec = (const unsigned char *)spec;
+ r = (hex[uspec[1]] << 4) | hex[uspec[2]];
+ g = (hex[uspec[3]] << 4) | hex[uspec[4]];
+ b = (hex[uspec[5]] << 4) | hex[uspec[6]];
+ } else if (!strcasecmp(spec,"black")) {
+ // r = g = b = 0;
+ } else if (!strcasecmp(spec,"white")) {
+ r = g = b = 255;
+ } else if (!strcasecmp(spec,"red")) {
+ r = 255;
+ } else if (!strcasecmp(spec,"green")) {
+ g = 255;
+ } else if (!strcasecmp(spec,"blue")) {
+ b = 255;
+ } else if (!strcasecmp(spec,"cyan")) {
+ g = b = 255;
+ } else if (!strcasecmp(spec,"magenta")) {
+ r = b = 255;
+ } else if (!strcasecmp(spec,"yellow")) {
+ r = g = 255;
+ } else {
+ return 0;
+ }
+
+ ret->red = (r << 8) | r;
+ ret->green = (g << 8) | g;
+ ret->blue = (b << 8) | b;
+ ret->flags = DoRed|DoGreen|DoBlue;
+ return 1;
+}
+
+Status
+XAllocNamedColor (Display *dpy, Colormap cmap, char *name,
+ XColor *screen_ret, XColor *exact_ret)
+{
+ if (! XParseColor (dpy, cmap, name, screen_ret))
+ return False;
+ *exact_ret = *screen_ret;
+ return XAllocColor (dpy, cmap, screen_ret);
+}
+
+int
+XQueryColor (Display *dpy, Colormap cmap, XColor *color)
+{
+ jwxyz_validate_pixel (dpy, color->pixel, visual_depth (NULL, NULL), False);
+ uint16_t rgba[4];
+ JWXYZ_QUERY_COLOR (dpy, color->pixel, 0xffffull, rgba);
+ color->red = rgba[0];
+ color->green = rgba[1];
+ color->blue = rgba[2];
+ color->flags = DoRed|DoGreen|DoBlue;
+ return 0;
+}
+
+int
+XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ XQueryColor (dpy, cmap, &c[i]);
+ return 0;
+}
+
+
+static unsigned long
+ximage_getpixel_1 (XImage *ximage, int x, int y)
+{
+ return ((ximage->data [y * ximage->bytes_per_line + (x>>3)] >> (x & 7)) & 1);
+}
+
+static int
+ximage_putpixel_1 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ if (pixel)
+ ximage->data [y * ximage->bytes_per_line + (x>>3)] |= (1 << (x & 7));
+ else
+ ximage->data [y * ximage->bytes_per_line + (x>>3)] &= ~(1 << (x & 7));
+
+ return 0;
+}
+
+static unsigned long
+ximage_getpixel_8 (XImage *ximage, int x, int y)
+{
+ return ((unsigned long)
+ *((uint8_t *) ximage->data +
+ (y * ximage->bytes_per_line) +
+ x));
+}
+
+static int
+ximage_putpixel_8 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ *((uint8_t *) ximage->data +
+ (y * ximage->bytes_per_line) +
+ x) = (uint8_t) pixel;
+ return 0;
+}
+
+
+static unsigned long
+ximage_getpixel_32 (XImage *ximage, int x, int y)
+{
+ return ((unsigned long)
+ *((uint32_t *) ximage->data +
+ (y * (ximage->bytes_per_line >> 2)) +
+ x));
+}
+
+static int
+ximage_putpixel_32 (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ *((uint32_t *) ximage->data +
+ (y * (ximage->bytes_per_line >> 2)) +
+ x) = (uint32_t) pixel;
+ return 0;
+}
+
+
+Status
+XInitImage (XImage *ximage)
+{
+ if (!ximage->bytes_per_line)
+ ximage->bytes_per_line = (ximage->depth == 1 ? (ximage->width + 7) / 8 :
+ ximage->depth == 8 ? ximage->width :
+ ximage->width * 4);
+
+ if (ximage->depth == 1) {
+ ximage->f.put_pixel = ximage_putpixel_1;
+ ximage->f.get_pixel = ximage_getpixel_1;
+ } else if (ximage->depth == 32 || ximage->depth == 24) {
+ ximage->f.put_pixel = ximage_putpixel_32;
+ ximage->f.get_pixel = ximage_getpixel_32;
+ } else if (ximage->depth == 8) {
+ ximage->f.put_pixel = ximage_putpixel_8;
+ ximage->f.get_pixel = ximage_getpixel_8;
+ } else {
+ Assert (0, "unknown depth");
+ }
+ return 1;
+}
+
+
+XImage *
+XCreateImage (Display *dpy, Visual *visual, unsigned int depth,
+ int format, int offset, char *data,
+ unsigned int width, unsigned int height,
+ int bitmap_pad, int bytes_per_line)
+{
+ XImage *ximage = (XImage *) calloc (1, sizeof(*ximage));
+ ximage->width = width;
+ ximage->height = height;
+ ximage->format = format;
+ ximage->data = data;
+ ximage->bitmap_unit = 8;
+ ximage->byte_order = LSBFirst;
+ ximage->bitmap_bit_order = ximage->byte_order;
+ ximage->bitmap_pad = bitmap_pad;
+ ximage->depth = depth;
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay (dpy));
+ ximage->red_mask = (depth == 1 ? 0 : v->red_mask);
+ ximage->green_mask = (depth == 1 ? 0 : v->green_mask);
+ ximage->blue_mask = (depth == 1 ? 0 : v->blue_mask);
+ ximage->bits_per_pixel = (depth == 1 ? 1 : visual_depth (NULL, NULL));
+ ximage->bytes_per_line = bytes_per_line;
+
+ XInitImage (ximage);
+ return ximage;
+}
+
+XImage *
+XSubImage (XImage *from, int x, int y, unsigned int w, unsigned int h)
+{
+ XImage *to = (XImage *) malloc (sizeof(*to));
+ memcpy (to, from, sizeof(*from));
+ to->width = w;
+ to->height = h;
+ to->bytes_per_line = 0;
+ XInitImage (to);
+
+ to->data = (char *) malloc (h * to->bytes_per_line);
+
+ if (x >= from->width)
+ w = 0;
+ else if (x+w > from->width)
+ w = from->width - x;
+
+ if (y >= from->height)
+ h = 0;
+ else if (y+h > from->height)
+ h = from->height - y;
+
+ int tx, ty;
+ for (ty = 0; ty < h; ty++)
+ for (tx = 0; tx < w; tx++)
+ XPutPixel (to, tx, ty, XGetPixel (from, x+tx, y+ty));
+ return to;
+}
+
+
+XPixmapFormatValues *
+XListPixmapFormats (Display *dpy, int *n_ret)
+{
+ XPixmapFormatValues *ret = calloc (2, sizeof(*ret));
+ ret[0].depth = visual_depth (NULL, NULL);
+ ret[0].bits_per_pixel = 32;
+ ret[0].scanline_pad = 8;
+ ret[1].depth = 1;
+ ret[1].bits_per_pixel = 1;
+ ret[1].scanline_pad = 8;
+ *n_ret = 2;
+ return ret;
+}
+
+
+unsigned long
+XGetPixel (XImage *ximage, int x, int y)
+{
+ return ximage->f.get_pixel (ximage, x, y);
+}
+
+
+int
+XPutPixel (XImage *ximage, int x, int y, unsigned long pixel)
+{
+ return ximage->f.put_pixel (ximage, x, y, pixel);
+}
+
+int
+XDestroyImage (XImage *ximage)
+{
+ if (ximage->data) free (ximage->data);
+ free (ximage);
+ return 0;
+}
+
+
+XImage *
+XGetImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format)
+{
+ unsigned depth = jwxyz_drawable_depth (d);
+ XImage *image = XCreateImage (dpy, 0, depth, format, 0, 0, width, height,
+ 0, 0);
+ image->data = (char *) malloc (height * image->bytes_per_line);
+
+ return XGetSubImage (dpy, d, x, y, width, height, plane_mask, format,
+ image, 0, 0);
+}
+
+
+Pixmap
+XCreatePixmapFromBitmapData (Display *dpy, Drawable drawable,
+ const char *data,
+ unsigned int w, unsigned int h,
+ unsigned long fg, unsigned long bg,
+ unsigned int depth)
+{
+ Pixmap p = XCreatePixmap (dpy, drawable, w, h, depth);
+ XImage *image = XCreateImage (dpy, 0, 1, XYPixmap, 0,
+ (char *) data, w, h, 0, 0);
+ XGCValues gcv;
+ gcv.foreground = fg;
+ gcv.background = bg;
+ GC gc = XCreateGC (dpy, p, GCForeground|GCBackground, &gcv);
+ XPutImage (dpy, p, gc, image, 0, 0, 0, 0, w, h);
+ XFreeGC (dpy, gc);
+ image->data = 0;
+ XDestroyImage (image);
+ return p;
+}
+
+
+char *
+XGetAtomName (Display *dpy, Atom atom)
+{
+ if (atom == XA_FONT)
+ return strdup ("FONT");
+
+ // Note that atoms (that aren't predefined) are just char *.
+ return strdup ((char *) atom);
+}
+
+
+// This is XQueryFont, but for the XFontStruct embedded in 'Font'
+//
+static void
+query_font (Font fid)
+{
+ Assert (fid && fid->native_font, "no native font in fid");
+
+ int first = 32;
+ int last = 255;
+
+ Display *dpy = fid->dpy;
+ void *native_font = fid->native_font;
+
+ XFontStruct *f = &fid->metrics;
+ XCharStruct *min = &f->min_bounds;
+ XCharStruct *max = &f->max_bounds;
+
+ f->fid = fid;
+ f->min_char_or_byte2 = first;
+ f->max_char_or_byte2 = last;
+ f->default_char = 'M';
+ f->ascent = fid->ascent;
+ f->descent = fid->descent;
+
+ min->width = 32767; // set to smaller values in the loop
+ min->ascent = 32767;
+ min->descent = 32767;
+ min->lbearing = 32767;
+ min->rbearing = 32767;
+
+ f->per_char = (XCharStruct *) calloc (last-first+2, sizeof (XCharStruct));
+
+ for (int i = first; i <= last; i++) {
+ XCharStruct *cs = &f->per_char[i-first];
+ char s = (char) i;
+ jwxyz_render_text (dpy, native_font, &s, 1, False, False, cs, 0);
+
+ max->width = MAX (max->width, cs->width);
+ max->ascent = MAX (max->ascent, cs->ascent);
+ max->descent = MAX (max->descent, cs->descent);
+ max->lbearing = MAX (max->lbearing, cs->lbearing);
+ max->rbearing = MAX (max->rbearing, cs->rbearing);
+
+ min->width = MIN (min->width, cs->width);
+ min->ascent = MIN (min->ascent, cs->ascent);
+ min->descent = MIN (min->descent, cs->descent);
+ min->lbearing = MIN (min->lbearing, cs->lbearing);
+ min->rbearing = MIN (min->rbearing, cs->rbearing);
+/*
+ Log (" %3d %c: w=%3d lb=%3d rb=%3d as=%3d ds=%3d "
+ " bb=%5.1f x %5.1f @ %5.1f %5.1f adv=%5.1f %5.1f\n"
+ i, i, cs->width, cs->lbearing, cs->rbearing,
+ cs->ascent, cs->descent,
+ bbox.size.width, bbox.size.height,
+ bbox.origin.x, bbox.origin.y,
+ advancement.width, advancement.height);
+ */
+ }
+}
+
+
+// Since 'Font' includes the metrics, this just makes a copy of that.
+//
+XFontStruct *
+XQueryFont (Display *dpy, Font fid)
+{
+ // copy XFontStruct
+ XFontStruct *f = (XFontStruct *) calloc (1, sizeof(*f));
+ *f = fid->metrics;
+ f->fid = fid;
+
+ // build XFontProps
+ f->n_properties = 1;
+ f->properties = malloc (sizeof(*f->properties) * f->n_properties);
+ f->properties[0].name = XA_FONT;
+ Assert (sizeof (f->properties[0].card32) >= sizeof (char *),
+ "atoms probably needs a real implementation");
+ // If XInternAtom is ever implemented, use it here.
+ f->properties[0].card32 = (unsigned long)fid->xa_font;
+
+ // copy XCharStruct array
+ int size = (f->max_char_or_byte2 - f->min_char_or_byte2) + 1;
+ f->per_char = (XCharStruct *) calloc (size + 2, sizeof (XCharStruct));
+
+ memcpy (f->per_char, fid->metrics.per_char,
+ size * sizeof (XCharStruct));
+
+ return f;
+}
+
+
+static Font
+copy_font (Font fid)
+{
+ fid->refcount++;
+ return fid;
+}
+
+
+/* On Cocoa and iOS, fonts may be specified as "Georgia Bold 24" instead
+ of XLFD strings; also they can be comma-separated strings with multiple
+ font names. First one that exists wins.
+ */
+static void
+try_native_font (Display *dpy, const char *name, Font fid)
+{
+ if (!name) return;
+ const char *spc = strrchr (name, ' ');
+ if (!spc) return;
+
+ char *token = strdup (name);
+ char *otoken = token;
+ char *name2;
+ char *lasts;
+
+ while ((name2 = strtok_r (token, ",", &lasts))) {
+ token = 0;
+
+ while (*name2 == ' ' || *name2 == '\t' || *name2 == '\n')
+ name2++;
+
+ spc = strrchr (name2, ' ');
+ if (!spc) continue;
+
+ int dsize = 0;
+ if (1 != sscanf (spc, " %d ", &dsize))
+ continue;
+ float size = dsize;
+
+ if (size < 4) continue;
+
+ name2[strlen(name2) - strlen(spc)] = 0;
+
+ fid->native_font = jwxyz_load_native_font(XRootWindow(dpy,0), 0, 0, name2,
+ strlen(name2) - strlen(spc),
+ JWXYZ_FONT_FACE, size, NULL,
+ &fid->ascent, &fid->descent);
+ if (fid->native_font) {
+ fid->xa_font = strdup (name); // Maybe this should be an XLFD?
+ break;
+ } else {
+ // To list fonts:
+ // po [UIFont familyNames]
+ // po [UIFont fontNamesForFamilyName:@"Arial"]
+ Log("No native font: \"%s\" %.0f", name2, size);
+ }
+ }
+
+ free (otoken);
+}
+
+
+static const char *
+xlfd_field_end (const char *s)
+{
+ const char *s2 = strchr(s, '-');
+ if (!s2)
+ s2 = s + strlen(s);
+ return s2;
+}
+
+
+static size_t
+xlfd_next (const char **s, const char **s2)
+{
+ if (!**s2) {
+ *s = *s2;
+ } else {
+ Assert (**s2 == '-', "xlfd parse error");
+ *s = *s2 + 1;
+ *s2 = xlfd_field_end (*s);
+ }
+
+ return *s2 - *s;
+}
+
+
+static void
+try_xlfd_font (Display *dpy, const char *name, Font fid)
+{
+ const char *family_name = NULL; /* Not NULL-terminated. */
+ size_t family_name_size = 0;
+ int require = 0,
+ // Default mask is for the built-in X11 font aliases.
+ mask = JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD | JWXYZ_STYLE_ITALIC;
+ Bool rand = False;
+ float size = 12; /* In points (1/72 in.) */
+
+ const char *s = (name ? name : "");
+
+ size_t L = strlen (s);
+# define CMP(STR) (L == strlen(STR) && !strncasecmp (s, (STR), L))
+# define UNSPEC (L == 0 || (L == 1 && *s == '*'))
+ if (CMP ("6x10")) size = 8, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("6x10bold")) size = 8, require |= JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD;
+ else if (CMP ("fixed")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("9x15")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("9x15bold")) size = 12, require |= JWXYZ_STYLE_MONOSPACE | JWXYZ_STYLE_BOLD;
+ else if (CMP ("vga")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("console")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("gallant")) size = 12, require |= JWXYZ_STYLE_MONOSPACE;
+ else {
+
+ int forbid = 0;
+
+ // Incorrect fields are ignored.
+
+ if (*s == '-')
+ ++s;
+ const char *s2 = xlfd_field_end(s);
+
+ // Foundry (ignore)
+
+ L = xlfd_next (&s, &s2); // Family name
+ // This used to substitute Georgia for Times. Now it doesn't.
+ if (CMP ("random")) {
+ rand = True;
+ } else if (CMP ("fixed")) {
+ require |= JWXYZ_STYLE_MONOSPACE;
+ family_name = "Courier";
+ family_name_size = strlen(family_name);
+ } else if (!UNSPEC) {
+ family_name = s;
+ family_name_size = L;
+ }
+
+ L = xlfd_next (&s, &s2); // Weight name
+ if (CMP ("bold") || CMP ("demibold"))
+ require |= JWXYZ_STYLE_BOLD;
+ else if (CMP ("medium") || CMP ("regular"))
+ forbid |= JWXYZ_STYLE_BOLD;
+
+ L = xlfd_next (&s, &s2); // Slant
+ if (CMP ("i") || CMP ("o"))
+ require |= JWXYZ_STYLE_ITALIC;
+ else if (CMP ("r"))
+ forbid |= JWXYZ_STYLE_ITALIC;
+
+ xlfd_next (&s, &s2); // Set width name (ignore)
+ xlfd_next (&s, &s2); // Add style name (ignore)
+
+ L = xlfd_next (&s, &s2); // Pixel size
+ char *s3;
+ uintmax_t pxsize = strtoumax(s, &s3, 10);
+ if (UNSPEC || s2 != s3)
+ pxsize = UINTMAX_MAX; // i.e. it's invalid.
+
+ L = xlfd_next (&s, &s2); // Point size
+ uintmax_t ptsize = strtoumax(s, &s3, 10);
+ if (UNSPEC || s2 != s3)
+ ptsize = UINTMAX_MAX;
+
+ xlfd_next (&s, &s2); // Resolution X (ignore)
+ xlfd_next (&s, &s2); // Resolution Y (ignore)
+
+ L = xlfd_next (&s, &s2); // Spacing
+ if (CMP ("p"))
+ forbid |= JWXYZ_STYLE_MONOSPACE;
+ else if (CMP ("m") || CMP ("c"))
+ require |= JWXYZ_STYLE_MONOSPACE;
+
+ xlfd_next (&s, &s2); // Average width (ignore)
+
+ // -*-courier-bold-r-*-*-14-*-*-*-*-*-*-* 14 px
+ // -*-courier-bold-r-*-*-*-140-*-*-m-*-*-* 14 pt
+ // -*-courier-bold-r-*-*-140-* 14 pt, via wildcard
+ // -*-courier-bold-r-*-140-* 14 pt, not handled
+ // -*-courier-bold-r-*-*-14-180-*-*-*-*-*-* error
+
+ L = xlfd_next (&s, &s2); // Charset registry
+ if (ptsize != UINTMAX_MAX) {
+ // It was in the ptsize field, so that's definitely what it is.
+ size = ptsize / 10.0;
+ } else if (pxsize != UINTMAX_MAX) {
+ size = pxsize;
+ // If it's a fully qualified XLFD, then this really is the pxsize.
+ // Otherwise, this is probably point size with a multi-field wildcard.
+ if (L == 0)
+ size /= 10.0;
+ }
+
+ mask = require | forbid;
+ }
+# undef CMP
+# undef UNSPEC
+
+ if (!family_name && !rand) {
+ family_name = jwxyz_default_font_family (require);
+ family_name_size = strlen (family_name);
+ }
+
+ if (size < 6 || size > 1000)
+ size = 12;
+
+ char *family_name_ptr = NULL;
+ fid->native_font = jwxyz_load_native_font (XRootWindow(dpy,0),
+ require, mask,
+ family_name, family_name_size,
+ rand ? JWXYZ_FONT_RANDOM : JWXYZ_FONT_FAMILY,
+ size, &family_name_ptr,
+ &fid->ascent, &fid->descent);
+
+ if (fid->native_font) {
+ unsigned dpi_d = XDisplayHeightMM (dpy,0) * 10 / 2;
+ unsigned dpi = (254 * XDisplayHeight (dpy,0) + dpi_d) / (2 * dpi_d);
+ asprintf(&fid->xa_font, "-*-%s-%s-%c-*-*-%u-%u-%u-%u-%c-0-iso10646-1",
+ family_name_ptr,
+ (require & JWXYZ_STYLE_BOLD) ? "bold" : "medium",
+ (require & JWXYZ_STYLE_ITALIC) ? 'o' : 'r',
+ (unsigned)(dpi * size / 72.27 + 0.5),
+ (unsigned)(size * 10 + 0.5), dpi, dpi,
+ (require & JWXYZ_STYLE_MONOSPACE) ? 'm' : 'p');
+ }
+
+ free (family_name_ptr);
+}
+
+
+Font
+XLoadFont (Display *dpy, const char *name)
+{
+ Font fid = (Font) calloc (1, sizeof(*fid));
+
+ fid->refcount = 1;
+ fid->dpy = dpy;
+ try_native_font (dpy, name, fid);
+
+ if (!fid->native_font && name &&
+ strchr (name, ' ') &&
+ !strchr (name, '*')) {
+ // If name contains a space but no stars, it is a native font spec --
+ // return NULL so that we know it really didn't exist. Else, it is an
+ // XLFD font, so keep trying.
+ free (fid);
+ return 0;
+ }
+
+ if (! fid->native_font)
+ try_xlfd_font (dpy, name, fid);
+
+ if (!fid->native_font) {
+ free (fid);
+ return 0;
+ }
+
+ query_font (fid);
+
+ return fid;
+}
+
+
+XFontStruct *
+XLoadQueryFont (Display *dpy, const char *name)
+{
+ Font fid = XLoadFont (dpy, name);
+ if (!fid) return 0;
+ return XQueryFont (dpy, fid);
+}
+
+int
+XUnloadFont (Display *dpy, Font fid)
+{
+ if (--fid->refcount < 0) abort();
+ if (fid->refcount > 0) return 0;
+
+ if (fid->native_font)
+ jwxyz_release_native_font (fid->dpy, fid->native_font);
+
+ if (fid->metrics.per_char)
+ free (fid->metrics.per_char);
+
+ free (fid);
+ return 0;
+}
+
+int
+XFreeFontInfo (char **names, XFontStruct *info, int n)
+{
+ int i;
+ if (names) {
+ for (i = 0; i < n; i++)
+ if (names[i]) free (names[i]);
+ free (names);
+ }
+ if (info) {
+ for (i = 0; i < n; i++)
+ if (info[i].per_char) {
+ free (info[i].per_char);
+ free (info[i].properties);
+ }
+ free (info);
+ }
+ return 0;
+}
+
+int
+XFreeFont (Display *dpy, XFontStruct *f)
+{
+ Font fid = f->fid;
+ XFreeFontInfo (0, f, 1);
+ XUnloadFont (dpy, fid);
+ return 0;
+}
+
+
+int
+XSetFont (Display *dpy, GC gc, Font fid)
+{
+ XGCValues *gcv = VTBL->gc_gcv(gc);
+ Font font2 = copy_font (fid);
+ if (gcv->font)
+ XUnloadFont (dpy, gcv->font);
+ gcv->font = font2;
+ return 0;
+}
+
+
+XFontSet
+XCreateFontSet (Display *dpy, char *name,
+ char ***missing_charset_list_return,
+ int *missing_charset_count_return,
+ char **def_string_return)
+{
+ char *name2 = strdup (name);
+ char *s = strchr (name, ',');
+ if (s) *s = 0;
+ XFontSet set = 0;
+ XFontStruct *f = XLoadQueryFont (dpy, name2);
+ if (f)
+ {
+ set = (XFontSet) calloc (1, sizeof(*set));
+ set->font = f;
+ }
+ free (name2);
+ if (missing_charset_list_return) *missing_charset_list_return = 0;
+ if (missing_charset_count_return) *missing_charset_count_return = 0;
+ if (def_string_return) *def_string_return = 0;
+ return set;
+}
+
+
+void
+XFreeFontSet (Display *dpy, XFontSet set)
+{
+ XFreeFont (dpy, set->font);
+ free (set);
+}
+
+
+void
+XFreeStringList (char **list)
+{
+ int i;
+ if (!list) return;
+ for (i = 0; list[i]; i++)
+ XFree (list[i]);
+ XFree (list);
+}
+
+
+int
+XTextExtents (XFontStruct *f, const char *s, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *cs)
+{
+ // Unfortunately, adding XCharStructs together to get the extents for a
+ // string doesn't work: Cocoa uses non-integral character advancements, but
+ // XCharStruct.width is an integer. Plus that doesn't take into account
+ // kerning pairs, alternate glyphs, and fun stuff like the word "Zapfino" in
+ // Zapfino.
+
+ Font ff = f->fid;
+ Display *dpy = ff->dpy;
+ jwxyz_render_text (dpy, ff->native_font, s, length, False, False, cs, 0);
+ *dir_ret = 0;
+ *ascent_ret = f->ascent;
+ *descent_ret = f->descent;
+ return 0;
+}
+
+int
+XTextWidth (XFontStruct *f, const char *s, int length)
+{
+ int ascent, descent, dir;
+ XCharStruct cs;
+ XTextExtents (f, s, length, &dir, &ascent, &descent, &cs);
+ return cs.width;
+}
+
+
+int
+XTextExtents16 (XFontStruct *f, const XChar2b *s, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *cs)
+{
+ // Bool latin1_p = True;
+ int i, utf8_len = 0;
+ char *utf8 = XChar2b_to_utf8 (s, &utf8_len); // already sanitized
+
+ for (i = 0; i < length; i++)
+ if (s[i].byte1 > 0) {
+ // latin1_p = False;
+ break;
+ }
+
+ {
+ Font ff = f->fid;
+ Display *dpy = ff->dpy;
+ jwxyz_render_text (dpy, ff->native_font, utf8, strlen(utf8),
+ True, False, cs, 0);
+ }
+
+ *dir_ret = 0;
+ *ascent_ret = f->ascent;
+ *descent_ret = f->descent;
+ free (utf8);
+ return 0;
+}
+
+
+/* "Returns the distance in pixels in the primary draw direction from
+ the drawing origin to the origin of the next character to be drawn."
+
+ "overall_ink_return is set to the bbox of the string's character ink."
+
+ "The overall_ink_return for a nondescending, horizontally drawn Latin
+ character is conventionally entirely above the baseline; that is,
+ overall_ink_return.height <= -overall_ink_return.y."
+
+ [So this means that y is the top of the ink, and height grows down:
+ For above-the-baseline characters, y is negative.]
+
+ "The overall_ink_return for a nonkerned character is entirely at, and to
+ the right of, the origin; that is, overall_ink_return.x >= 0."
+
+ [So this means that x is the left of the ink, and width grows right.
+ For left-of-the-origin characters, x is negative.]
+
+ "A character consisting of a single pixel at the origin would set
+ overall_ink_return fields y = 0, x = 0, width = 1, and height = 1."
+ */
+int
+Xutf8TextExtents (XFontSet set, const char *str, int len,
+ XRectangle *overall_ink_return,
+ XRectangle *overall_logical_return)
+{
+ XCharStruct cs;
+ Font f = set->font->fid;
+
+ jwxyz_render_text (f->dpy, f->native_font, str, len, True, False, &cs,
+ NULL);
+
+ /* "The overall_logical_return is the bounding box that provides minimum
+ spacing to other graphical features for the string. Other graphical
+ features, for example, a border surrounding the text, should not
+ intersect this rectangle."
+
+ So I think that means they're the same? Or maybe "ink" is the bounding
+ box, and "logical" is the advancement? But then why is the return value
+ the advancement?
+ */
+ if (overall_ink_return)
+ XCharStruct_to_XmbRectangle (cs, *overall_ink_return);
+ if (overall_logical_return)
+ XCharStruct_to_XmbRectangle (cs, *overall_logical_return);
+
+ return cs.width;
+}
+
+
+int
+jwxyz_draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, size_t len, int utf8_p)
+{
+ const XGCValues *gcv = VTBL->gc_gcv (gc);
+ Font ff = gcv->font;
+ XCharStruct cs;
+
+ char *data = 0;
+ jwxyz_render_text (dpy, jwxyz_native_font (ff), str, len, utf8_p,
+ gcv->antialias_p, &cs, &data);
+ int w = cs.rbearing - cs.lbearing;
+ int h = cs.ascent + cs.descent;
+
+ if (w < 0 || h < 0) abort();
+ if (w == 0 || h == 0) {
+ if (data) free(data);
+ return 0;
+ }
+
+ XImage *img = XCreateImage (dpy, VTBL->visual (dpy), 32,
+ ZPixmap, 0, data, w, h, 0, 0);
+
+ /* The image of text is a 32-bit image, in white.
+ Take the green channel for intensity and use that as alpha.
+ replace RGB with the GC's foreground color.
+ This expects that XPutImage respects alpha and only writes
+ the bits that are not masked out.
+ */
+ {
+# define ROTL(x, rot) (((x) << ((rot) & 31)) | ((x) >> (32 - ((rot) & 31))))
+
+ Visual *v = DefaultVisualOfScreen (DefaultScreenOfDisplay(dpy));
+ unsigned shift = (i_log2 (v->alpha_mask) - i_log2 (v->green_mask)) & 31;
+ uint32_t mask = ROTL(v->green_mask, shift) & v->alpha_mask,
+ color = gcv->foreground & ~v->alpha_mask;
+ uint32_t *s = (uint32_t *)data;
+ uint32_t *end = s + (w * h);
+ while (s < end) {
+
+ *s = (ROTL(*s, shift) & mask) | color;
+ ++s;
+ }
+ }
+
+ {
+ Bool old_alpha = gcv->alpha_allowed_p;
+ jwxyz_XSetAlphaAllowed (dpy, gc, True);
+ XPutImage (dpy, d, gc, img, 0, 0,
+ x + cs.lbearing,
+ y - cs.ascent,
+ w, h);
+ jwxyz_XSetAlphaAllowed (dpy, gc, old_alpha);
+ XDestroyImage (img);
+ }
+
+ return 0;
+}
+
+
+int
+XDrawString (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, int len)
+{
+ return VTBL->draw_string (dpy, d, gc, x, y, str, len, False);
+}
+
+
+int
+XDrawString16 (Display *dpy, Drawable d, GC gc, int x, int y,
+ const XChar2b *str, int len)
+{
+ XChar2b *b2 = malloc ((len + 1) * sizeof(*b2));
+ char *s2;
+ int ret;
+ memcpy (b2, str, len * sizeof(*b2));
+ b2[len].byte1 = b2[len].byte2 = 0;
+ s2 = XChar2b_to_utf8 (b2, 0);
+ free (b2);
+ ret = VTBL->draw_string (dpy, d, gc, x, y, s2, strlen(s2), True);
+ free (s2);
+ return ret;
+}
+
+
+void
+Xutf8DrawString (Display *dpy, Drawable d, XFontSet set, GC gc,
+ int x, int y, const char *str, int len)
+{
+ VTBL->draw_string (dpy, d, gc, x, y, str, len, True);
+}
+
+
+int
+XDrawImageString (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, int len)
+{
+ int ascent, descent, dir;
+ XCharStruct cs;
+ XTextExtents (&VTBL->gc_gcv (gc)->font->metrics, str, len,
+ &dir, &ascent, &descent, &cs);
+ jwxyz_fill_rect (dpy, d, gc,
+ x + MIN (0, cs.lbearing),
+ y - MAX (0, ascent),
+
+ /* The +1 here is almost certainly wrong, but BSOD
+ requires it; and only BSOD, fluidballs, juggle
+ and grabclient call XDrawImageString... */
+ MAX (MAX (0, cs.rbearing) -
+ MIN (0, cs.lbearing),
+ cs.width) + 1,
+ MAX (0, ascent) + MAX (0, descent),
+ VTBL->gc_gcv(gc)->background);
+ return XDrawString (dpy, d, gc, x, y, str, len);
+}
+
+
+void *
+jwxyz_native_font (Font f)
+{
+ return f->native_font;
+}
+
+
+int
+XSetForeground (Display *dpy, GC gc, unsigned long fg)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ jwxyz_validate_pixel (dpy, fg, VTBL->gc_depth (gc), gcv->alpha_allowed_p);
+ gcv->foreground = fg;
+ return 0;
+}
+
+
+int
+XSetBackground (Display *dpy, GC gc, unsigned long bg)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ jwxyz_validate_pixel (dpy, bg, VTBL->gc_depth (gc), gcv->alpha_allowed_p);
+ gcv->background = bg;
+ return 0;
+}
+
+int
+jwxyz_XSetAlphaAllowed (Display *dpy, GC gc, Bool allowed)
+{
+ VTBL->gc_gcv (gc)->alpha_allowed_p = allowed;
+ return 0;
+}
+
+int
+jwxyz_XSetAntiAliasing (Display *dpy, GC gc, Bool antialias_p)
+{
+ VTBL->gc_gcv (gc)->antialias_p = antialias_p;
+ return 0;
+}
+
+
+int
+XSetLineAttributes (Display *dpy, GC gc, unsigned int line_width,
+ int line_style, int cap_style, int join_style)
+{
+ XGCValues *gcv = VTBL->gc_gcv (gc);
+ gcv->line_width = line_width;
+ Assert (line_style == LineSolid, "only LineSolid implemented");
+// gc->gcv.line_style = line_style;
+ gcv->cap_style = cap_style;
+ gcv->join_style = join_style;
+ return 0;
+}
+
+int
+XSetGraphicsExposures (Display *dpy, GC gc, Bool which)
+{
+ return 0;
+}
+
+int
+XSetFunction (Display *dpy, GC gc, int which)
+{
+ VTBL->gc_gcv (gc)->function = which;
+ return 0;
+}
+
+int
+XSetSubwindowMode (Display *dpy, GC gc, int which)
+{
+ VTBL->gc_gcv (gc)->subwindow_mode = which;
+ return 0;
+}
+
+
+Bool
+XQueryPointer (Display *dpy, Window w, Window *root_ret, Window *child_ret,
+ int *root_x_ret, int *root_y_ret,
+ int *win_x_ret, int *win_y_ret, unsigned int *mask_ret)
+{
+ assert_window (dpy, w);
+
+ XPoint vpos, p;
+ jwxyz_get_pos (w, &vpos, &p);
+
+ if (root_x_ret) *root_x_ret = p.x;
+ if (root_y_ret) *root_y_ret = p.y;
+ if (win_x_ret) *win_x_ret = p.x - vpos.x;
+ if (win_y_ret) *win_y_ret = p.y - vpos.y;
+ if (mask_ret) *mask_ret = 0; // #### poll the keyboard modifiers?
+ if (root_ret) *root_ret = 0;
+ if (child_ret) *child_ret = 0;
+ return True;
+}
+
+Bool
+XTranslateCoordinates (Display *dpy, Window w, Window dest_w,
+ int src_x, int src_y,
+ int *dest_x_ret, int *dest_y_ret,
+ Window *child_ret)
+{
+ assert_window (dpy, w);
+
+ XPoint vpos, p;
+ jwxyz_get_pos (w, &vpos, NULL);
+
+ // point starts out relative to top left of view
+ p.x = src_x;
+ p.y = src_y;
+
+ // get point relative to top left of screen
+ p.x += vpos.x;
+ p.y += vpos.y;
+
+ *dest_x_ret = p.x;
+ *dest_y_ret = p.y;
+ if (child_ret)
+ *child_ret = w;
+ return True;
+}
+
+
+KeySym
+XKeycodeToKeysym (Display *dpy, KeyCode code, int index)
+{
+ return code;
+}
+
+int
+XLookupString (XKeyEvent *e, char *buf, int size, KeySym *k_ret,
+ XComposeStatus *xc)
+{
+ KeySym ks = XKeycodeToKeysym (0, e->keycode, 0);
+ char c = 0;
+ // Do not put non-ASCII KeySyms like XK_Shift_L and XK_Page_Up in the string.
+ if ((unsigned int) ks <= 255)
+ c = (char) ks;
+
+ // Put control characters in the string. Not meta.
+ if (e->state & ControlMask) {
+ if (c >= 'a' && c <= 'z') // Upcase control.
+ c -= 'a'-'A';
+ if (c >= '@' && c <= '_') // Shift to control page.
+ c -= '@';
+ if (c == ' ') // C-SPC is NULL.
+ c = 0;
+ }
+
+ if (k_ret) *k_ret = ks;
+ if (size > 0) buf[0] = c;
+ if (size > 1) buf[1] = 0;
+ return (size > 0 ? 1 : 0);
+}
+
+
+int
+XFlush (Display *dpy)
+{
+ // Just let the event loop take care of this on its own schedule.
+ return 0;
+}
+
+int
+XSync (Display *dpy, Bool flush)
+{
+ return XFlush (dpy);
+}
+
+
+// declared in utils/visual.h
+int
+has_writable_cells (Screen *s, Visual *v)
+{
+ return 0;
+}
+
+int
+visual_depth (Screen *s, Visual *v)
+{
+ return 32;
+}
+
+int
+visual_cells (Screen *s, Visual *v)
+{
+ return (int)(v->red_mask | v->green_mask | v->blue_mask);
+}
+
+int
+visual_class (Screen *s, Visual *v)
+{
+ return TrueColor;
+}
+
+void
+visual_rgb_masks (Screen *s, Visual *v, unsigned long *red_mask,
+ unsigned long *green_mask, unsigned long *blue_mask)
+{
+ *red_mask = v->red_mask;
+ *green_mask = v->green_mask;
+ *blue_mask = v->blue_mask;
+}
+
+int
+visual_pixmap_depth (Screen *s, Visual *v)
+{
+ return 32;
+}
+
+int
+screen_number (Screen *screen)
+{
+ return 0;
+}
+
+// declared in utils/grabclient.h
+Bool
+use_subwindow_mode_p (Screen *screen, Window window)
+{
+ return False;
+}
+
+#endif /* HAVE_JWXYZ */
diff --git a/jwxyz/jwxyz-gl.c b/jwxyz/jwxyz-gl.c
new file mode 100644
index 0000000..369d16f
--- /dev/null
+++ b/jwxyz/jwxyz-gl.c
@@ -0,0 +1,2125 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do OpenGL-ish things that bear some resemblance to the
+ things that Xlib might have done.
+
+ This is the version of jwxyz for Android. The version used by macOS
+ and iOS is in jwxyz.m.
+ */
+
+/* Be advised, this is all very much a work in progress. */
+
+/* There is probably no reason to ever implement indexed-color rendering here,
+ even if many screenhacks still work with PseudoColor.
+ - OpenGL ES 1.1 (Android, iOS) doesn't support indexed color.
+ - macOS only provides indexed color via AGL (now deprecated), not
+ NSOpenGLPixelFormat.
+ */
+
+/* TODO:
+ - malloc error checking
+ - Check max texture sizes for XGet/PutImage, XCopyArea.
+ - Optional 5:5:5 16-bit color
+*/
+
+/* Techniques & notes:
+ - iOS: OpenGL ES 2.0 isn't always available. Use OpenGL ES 1.1.
+ - OS X: Drivers can go back to OpenGL 1.1 (GeForce 2 MX on 10.5.8).
+ - Use stencil buffers (OpenGL 1.0+) for bitmap clipping masks.
+ - Pixmaps can be any of the following, depending on GL implementation.
+ - This requires offscreen rendering. Fortunately, this is always
+ available.
+ - OS X: Drawable objects, including: pixel buffers and offscreen
+ memory.
+ - Offscreen buffers w/ CGLSetOffScreen (10.0+)
+ - http://lists.apple.com/archives/mac-opengl/2002/Jun/msg00087.html
+ provides a very ugly solution for hardware-accelerated offscreen
+ rendering with CGLSetParameter(*, kCGLCPSurfaceTexture, *) on OS X
+ 10.1+. Apple docs say it's actually for OS X 10.3+, instead.
+ - Pixel buffers w/ CGLSetPBuffer (10.3+, now deprecated)
+ - Requires APPLE_pixel_buffer.
+ - Available in software on x86 only.
+ - Always available on hardware.
+ - Need to blit? Use OpenGL pixel buffers. (GL_PIXEL_PACK_BUFFER)
+ - Framebuffer objects w/ GL_(ARB|EXT)_framebuffer_object
+ - TODO: Can EXT_framebuffers be different sizes?
+ - Preferred on 10.7+
+ - iOS: Use OES_framebuffer_object, it's always present.
+ */
+
+/* OpenGL hacks call a number of X11 functions, including
+ XCopyArea, XDrawString, XGetImage
+ XCreatePixmap, XCreateGC, XCreateImage
+ XPutPixel
+ Check these, of course.
+ */
+
+#ifdef JWXYZ_GL /* entire file */
+
+#include <math.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <wchar.h>
+
+#ifdef HAVE_COCOA
+# ifdef USE_IPHONE
+# import <QuartzCore/QuartzCore.h>
+# include <OpenGLES/ES1/gl.h>
+# include <OpenGLES/ES1/glext.h>
+# else
+# include <OpenGL/glu.h>
+# endif
+#else
+/* TODO: Does this work on iOS? */
+# ifndef HAVE_JWZGLES
+# include <gl/glu.h>
+# else
+# include <GLES/gl.h>
+# include <GLES/glext.h>
+# endif
+#endif
+
+#ifdef HAVE_JWZGLES
+# include "jwzglesI.h"
+#endif
+
+#include "jwxyzI.h"
+#include "jwxyz-timers.h"
+#include "yarandom.h"
+#include "utf8wc.h"
+#include "xft.h"
+#include "pow2.h"
+
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+union color_bytes {
+ uint32_t pixel;
+ uint8_t bytes[4];
+};
+
+// Use two textures: one for RGBA, one for luminance. Older Android doesn't
+// seem to like it when textures change format.
+enum {
+ texture_rgba,
+ texture_mono
+};
+
+struct jwxyz_Display {
+ const struct jwxyz_vtbl *vtbl; // Must come first.
+
+ Window main_window;
+ GLenum pixel_format, pixel_type;
+ Visual visual;
+ struct jwxyz_sources_data *timers_data;
+
+ Bool gl_texture_npot_p;
+ /* Bool opengl_core_p */;
+ GLenum gl_texture_target;
+
+ GLuint textures[2]; // Also can work on the desktop.
+
+ unsigned long window_background;
+
+ int gc_function;
+ Bool gc_alpha_allowed_p;
+ int gc_clip_x_origin, gc_clip_y_origin;
+ GLuint gc_clip_mask;
+
+ // Alternately, there could be one queue per pixmap.
+ size_t queue_size, queue_capacity;
+ Drawable queue_drawable;
+ GLint queue_mode;
+ void *queue_vertex;
+ uint32_t *queue_color;
+ Bool queue_line_cap;
+};
+
+struct jwxyz_GC {
+ XGCValues gcv;
+ unsigned int depth;
+ GLuint clip_mask;
+ unsigned clip_mask_width, clip_mask_height;
+};
+
+struct jwxyz_linked_point {
+ short x, y;
+ linked_point *next;
+};
+
+
+void
+jwxyz_assert_display(Display *dpy)
+{
+ if(!dpy)
+ return;
+ jwxyz_assert_gl ();
+ jwxyz_assert_drawable (dpy->main_window, dpy->main_window);
+}
+
+
+void
+jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
+ Bool window_p)
+{
+ Assert (width, "no width");
+ Assert (height, "no height");
+
+ /* TODO: Check registration pattern from Interference with rectangles instead of points. */
+
+ // The projection matrix is always set as follows. The modelview matrix is
+ // usually identity, but for points and thin lines, it's translated by 0.5.
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+# if defined(USE_IPHONE) || defined(HAVE_ANDROID)
+
+ if (window_p && ignore_rotation_p(dpy)) {
+ int o = (int) current_device_rotation();
+ glRotatef (-o, 0, 0, 1);
+ }
+
+ // glPointSize(1); // This is the default.
+
+#ifdef HAVE_JWZGLES
+ glOrthof /* TODO: Is glOrthox worth it? Signs point to no. */
+#else
+ glOrtho
+#endif
+ (0, width, height, 0, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+# endif // HAVE_MOBILE
+}
+
+#ifndef HAVE_JWZGLES
+
+struct gl_version
+{
+ // iOS always uses OpenGL ES 1.1.
+ unsigned major;
+ unsigned minor;
+};
+
+static GLboolean
+gl_check_ver (const struct gl_version *caps,
+ unsigned gl_major,
+ unsigned gl_minor)
+{
+ return caps->major > gl_major ||
+ (caps->major == gl_major && caps->minor >= gl_minor);
+}
+
+#endif
+
+
+static void
+tex_parameters (Display *d, GLuint texture)
+{
+ // TODO: Check for (ARB|EXT|NV)_texture_rectangle. (All three are alike.)
+ // Rectangle textures should be present on OS X with the following exceptions:
+ // - Generic renderer on PowerPC OS X 10.4 and earlier
+ // - ATI Rage 128
+ glBindTexture (d->gl_texture_target, texture);
+ // TODO: This is all somewhere else. Refactor.
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // This might be redundant for rectangular textures.
+# ifndef HAVE_JWZGLES
+ const GLint wrap = GL_CLAMP;
+# else // HAVE_JWZGLES
+ const GLint wrap = GL_CLAMP_TO_EDGE;
+# endif // HAVE_JWZGLES
+
+ // In OpenGL, CLAMP_TO_EDGE is OpenGL 1.2 or GL_SGIS_texture_edge_clamp.
+ // This is always present with OpenGL ES.
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_S, wrap);
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_T, wrap);
+}
+
+static void
+tex_size (Display *dpy, unsigned *tex_w, unsigned *tex_h)
+{
+ if (!dpy->gl_texture_npot_p) {
+ *tex_w = to_pow2(*tex_w);
+ *tex_h = to_pow2(*tex_h);
+ }
+}
+
+static void
+tex_image (Display *dpy, GLenum internalformat,
+ unsigned *tex_w, unsigned *tex_h, GLenum format, GLenum type,
+ const void *data)
+{
+ unsigned w = *tex_w, h = *tex_h;
+ tex_size (dpy, tex_w, tex_h);
+
+ // TODO: Would using glTexSubImage2D exclusively be faster?
+ if (*tex_w == w && *tex_h == h) {
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
+ 0, format, type, data);
+ } else {
+ // TODO: Sampling the last row might be a problem if src_x != 0.
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
+ 0, format, type, NULL);
+ glTexSubImage2D (dpy->gl_texture_target, 0, 0, 0, w, h,
+ format, type, data);
+ }
+}
+
+
+extern const struct jwxyz_vtbl gl_vtbl;
+
+Display *
+jwxyz_gl_make_display (Window w)
+{
+ Display *d = (Display *) calloc (1, sizeof(*d));
+ d->vtbl = &gl_vtbl;
+
+# ifndef HAVE_JWZGLES
+ struct gl_version version;
+
+ {
+ const GLubyte *version_str = glGetString (GL_VERSION);
+
+ /* iPhone is always OpenGL ES 1.1. */
+ if (sscanf ((const char *) version_str, "%u.%u",
+ &version.major, &version.minor) < 2)
+ {
+ version.major = 1;
+ version.minor = 1;
+ }
+ }
+# endif // !HAVE_JWZGLES
+
+ const GLubyte *extensions = glGetString (GL_EXTENSIONS);
+
+ /* See:
+ - Apple TN2080: Understanding and Detecting OpenGL Functionality.
+ - OpenGL Programming Guide for the Mac - Best Practices for Working with
+ Texture Data - Optimal Data Formats and Types
+ */
+
+ // If a video adapter suports BGRA textures, then that's probably as fast as
+ // you're gonna get for getting a texture onto the screen.
+# ifdef HAVE_JWZGLES
+ /* TODO: Make BGRA work on iOS. As it is, it breaks XPutImage. (glTexImage2D, AFAIK) */
+ d->pixel_format = GL_RGBA; /*
+ gluCheckExtension ((const GLubyte *) "GL_APPLE_texture_format_BGRA8888",
+ extensions) ? GL_BGRA_EXT : GL_RGBA; */
+ d->pixel_type = GL_UNSIGNED_BYTE;
+ // See also OES_read_format.
+# else // !HAVE_JWZGLES
+ if (gl_check_ver (&version, 1, 2) ||
+ (gluCheckExtension ((const GLubyte *) "GL_EXT_bgra", extensions) &&
+ gluCheckExtension ((const GLubyte *) "GL_APPLE_packed_pixels",
+ extensions))) {
+ // APPLE_packed_pixels is only ever available on iOS, never Android.
+ d->pixel_format = GL_BGRA_EXT;
+ // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
+ d->pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ } else {
+ d->pixel_format = GL_RGBA;
+ d->pixel_type = GL_UNSIGNED_BYTE;
+ }
+ // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
+ // sense on PowerPC.
+# endif // !HAVE_JWZGLES
+
+ // On really old systems, it would make sense to split textures
+ // into subsections, to work around the maximum texture size.
+# ifndef HAVE_JWZGLES
+ d->gl_texture_npot_p = gluCheckExtension ((const GLubyte *)
+ "GL_ARB_texture_rectangle",
+ extensions);
+ d->gl_texture_target = d->gl_texture_npot_p ?
+ GL_TEXTURE_RECTANGLE_EXT :
+ GL_TEXTURE_2D;
+# else
+ d->gl_texture_npot_p = jwzgles_gluCheckExtension
+ ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions) ||
+ jwzgles_gluCheckExtension
+ ((const GLubyte *) "GL_OES_texture_npot", extensions) ||
+ jwzgles_gluCheckExtension // From PixelFlinger 1.4
+ ((const GLubyte *) "GL_ARB_texture_non_power_of_two", extensions);
+
+ d->gl_texture_target = GL_TEXTURE_2D;
+# endif
+
+ Visual *v = &d->visual;
+ v->class = TrueColor;
+ if (d->pixel_format == GL_BGRA_EXT) {
+ v->red_mask = 0x00ff0000;
+ v->green_mask = 0x0000ff00;
+ v->blue_mask = 0x000000ff;
+ v->alpha_mask = 0xff000000;
+ } else {
+ Assert(d->pixel_format == GL_RGBA,
+ "jwxyz_gl_make_display: Unknown pixel_format");
+ unsigned long masks[4];
+ for (unsigned i = 0; i != 4; ++i) {
+ union color_bytes color;
+ color.pixel = 0;
+ color.bytes[i] = 0xff;
+ masks[i] = color.pixel;
+ }
+ v->red_mask = masks[0];
+ v->green_mask = masks[1];
+ v->blue_mask = masks[2];
+ v->alpha_mask = masks[3];
+ }
+
+ d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
+
+ d->window_background = BlackPixel(d,0);
+
+ d->main_window = w;
+
+ {
+ GLint max_texture_size, max_texture_units;
+ glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
+ glGetIntegerv (GL_MAX_TEXTURE_UNITS, &max_texture_units);
+ Log ("GL_MAX_TEXTURE_SIZE: %d, GL_MAX_TEXTURE_UNITS: %d\n",
+ max_texture_size, max_texture_units);
+
+ // OpenGL ES 1.1 and OpenGL 1.3 both promise at least 2 texture units:
+ // OpenGL (R) ES Common/Common-Lite Profile Specification, Version 1.1.12 (Full Specification)
+ // https://www.khronos.org/registry/OpenGL/specs/es/1.1/es_full_spec_1.1.pdf
+ // * Table 6.22. Implementation Dependent Values
+ // * D.2 Enhanced Texture Processing
+ // (OpenGL 1.2 provides multitexturing as an ARB extension, and requires 1
+ // texture unit only.)
+
+ // ...but the glGet reference page contradicts this, and says there can be
+ // just one.
+ // https://www.khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/glGet.xml
+ }
+
+ glGenTextures (countof (d->textures), d->textures);
+
+ for (unsigned i = 0; i != countof (d->textures); i++) {
+ tex_parameters (d, d->textures[i]);
+ }
+
+ d->gc_function = GXcopy;
+ d->gc_alpha_allowed_p = False;
+ d->gc_clip_mask = 0;
+
+ jwxyz_assert_display(d);
+ return d;
+}
+
+void
+jwxyz_gl_free_display (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ /* TODO: Go over everything. */
+
+ free (dpy->queue_vertex);
+ free (dpy->queue_color);
+
+ jwxyz_sources_free (dpy->timers_data);
+
+ free (dpy);
+}
+
+
+/* Call this when the View changes size or position.
+ */
+void
+jwxyz_window_resized (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ const XRectangle *new_frame = jwxyz_frame (dpy->main_window);
+ unsigned new_width = new_frame->width;
+ unsigned new_height = new_frame->height;
+
+ Assert (new_width, "jwxyz_window_resized: No width.");
+ Assert (new_height, "jwxyz_window_resized: No height.");
+
+/*if (cgc) w->cgc = cgc;
+ Assert (w->cgc, "no CGContext"); */
+
+ Log("resize: %d, %d\n", new_width, new_height);
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, dpy->main_window);
+
+ // TODO: What does the iPhone need?
+
+ // iOS only: If the main_window is not the current_drawable, then set_matrices
+ // was already called in bind_drawable.
+ jwxyz_set_matrices (dpy, new_width, new_height, True);
+
+/*
+ GLint draw_buffer;
+ glGetIntegerv (GL_DRAW_BUFFER, &draw_buffer);
+
+ glDrawBuffer (GL_FRONT);
+ glClearColor (1, 0, 1, 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+ glDrawBuffer (GL_BACK);
+ glClearColor (0, 1, 1, 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+
+ glDrawBuffer (draw_buffer); */
+
+ // Stylish and attractive purple!
+ // glClearColor (1, 0, 1, 0.5);
+ // glClear (GL_COLOR_BUFFER_BIT);
+}
+
+
+static jwxyz_sources_data *
+display_sources_data (Display *dpy)
+{
+ return dpy->timers_data;
+}
+
+
+static Window
+root (Display *dpy)
+{
+ return dpy->main_window;
+}
+
+static Visual *
+visual (Display *dpy)
+{
+ return &dpy->visual;
+}
+
+
+/* GC attributes by usage and OpenGL implementation:
+
+ All drawing functions:
+ function | glLogicOp w/ GL_COLOR_LOGIC_OP
+ clip_x_origin, clip_y_origin, clip_mask | Multitexturing w/ GL_TEXTURE1
+
+ Shape drawing functions:
+ foreground, background | glColor*
+
+ XDrawLines, XDrawRectangles, XDrawSegments:
+ line_width, cap_style, join_style | Lotsa vertices
+
+ XFillPolygon:
+ fill_rule | Multiple GL_TRIANGLE_FANs
+
+ XDrawText:
+ font | Cocoa, then OpenGL display lists.
+
+ alpha_allowed_p | GL_BLEND
+
+ antialias_p | Well, there's options:
+ * Multisampling would work, but that's something that would need to be set
+ per-Pixmap, not per-GC.
+ * GL_POINT, LINE, and POLYGON_SMOOTH are the old-school way of doing
+ this, but POINT_SMOOTH is unnecessary, and POLYGON_SMOOTH is missing from
+ GLES 1. All three are missing from GLES 2. Word on the street is that
+ these are deprecated anyway.
+ * Tiny textures with bilinear filtering to get the same effect as LINE_ and
+ POLYGON_SMOOTH. A bit tricky.
+ * Do nothing. Android hardware is very often high-DPI enough that
+ anti-aliasing doesn't matter all that much.
+
+ Nothing, really:
+ subwindow_mode
+ */
+
+static void *
+enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count,
+ unsigned long pixel)
+{
+ if (dpy->queue_size &&
+ (!gc || /* Could allow NULL GCs here... */
+ dpy->gc_function != gc->gcv.function ||
+ dpy->gc_alpha_allowed_p != gc->gcv.alpha_allowed_p ||
+ dpy->gc_clip_mask != gc->clip_mask ||
+ dpy->gc_clip_x_origin != gc->gcv.clip_x_origin ||
+ dpy->gc_clip_y_origin != gc->gcv.clip_y_origin ||
+ dpy->queue_mode != mode ||
+ dpy->queue_drawable != d)) {
+ jwxyz_gl_flush (dpy);
+ }
+
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ if (mode == GL_TRIANGLE_STRIP)
+ Assert (count, "empty triangle strip");
+ // Use degenerate triangles to cut down on draw calls.
+ Bool prepend2 = mode == GL_TRIANGLE_STRIP && dpy->queue_size;
+
+ // ####: Primitive restarts should be used here when (if) they're available.
+ if (prepend2)
+ count += 2;
+
+ // TODO: Use glColor when we can get away with it.
+ size_t old_size = dpy->queue_size;
+ dpy->queue_size += count;
+ if (dpy->queue_size > dpy->queue_capacity) {
+ dpy->queue_capacity = dpy->queue_size * 2;
+
+ uint32_t *new_color = realloc (
+ dpy->queue_color, sizeof(*dpy->queue_color) * dpy->queue_capacity);
+ /* Allocate vertices as if they were always GLfloats. Otherwise, if
+ queue_vertex is allocated to hold GLshorts, then things get switched
+ to GLfloats, queue_vertex would be too small for the given capacity.
+ */
+ GLshort *new_vertex = realloc (
+ dpy->queue_vertex, sizeof(GLfloat) * 2 * dpy->queue_capacity);
+
+ if (!new_color || !new_vertex)
+ return NULL;
+
+ dpy->queue_color = new_color;
+ dpy->queue_vertex = new_vertex;
+ }
+
+ dpy->queue_mode = mode;
+ dpy->queue_drawable = d;
+
+ union color_bytes color;
+
+ // Like query_color, but for bytes.
+ jwxyz_validate_pixel (dpy, pixel, jwxyz_drawable_depth (d),
+ gc ? gc->gcv.alpha_allowed_p : False);
+
+ if (jwxyz_drawable_depth (d) == 1) {
+ uint8_t b = pixel ? 0xff : 0;
+ color.bytes[0] = b;
+ color.bytes[1] = b;
+ color.bytes[2] = b;
+ color.bytes[3] = 0xff;
+ } else {
+ JWXYZ_QUERY_COLOR (dpy, pixel, 0xffull, color.bytes);
+ }
+
+ for (size_t i = 0; i != count; ++i) // TODO: wmemset when applicable.
+ dpy->queue_color[i + old_size] = color.pixel;
+
+ void *result = (char *)dpy->queue_vertex + old_size * 2 *
+ (mode == GL_TRIANGLE_STRIP ? sizeof(GLfloat) : sizeof(GLshort));
+ if (prepend2) {
+ dpy->queue_color[old_size] = dpy->queue_color[old_size - 1];
+ result = (GLfloat *)result + 4;
+ }
+ return result;
+}
+
+
+static void
+finish_triangle_strip (Display *dpy, GLfloat *enqueue_result)
+{
+ if (enqueue_result != dpy->queue_vertex) {
+ enqueue_result[-4] = enqueue_result[-6];
+ enqueue_result[-3] = enqueue_result[-5];
+ enqueue_result[-2] = enqueue_result[0];
+ enqueue_result[-1] = enqueue_result[1];
+ }
+}
+
+
+static void
+query_color (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p, GLfloat *rgba)
+{
+ jwxyz_validate_pixel (dpy, pixel, depth, alpha_allowed_p);
+
+ if (depth == 1) {
+ GLfloat f = pixel;
+ rgba[0] = f;
+ rgba[1] = f;
+ rgba[2] = f;
+ rgba[3] = 1;
+ } else {
+ JWXYZ_QUERY_COLOR (dpy, pixel, 1.0f, rgba);
+ }
+}
+
+
+static void
+set_color (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p)
+{
+ GLfloat rgba[4];
+ query_color (dpy, pixel, depth, alpha_allowed_p, rgba);
+ glColor4f (rgba[0], rgba[1], rgba[2], rgba[3]);
+}
+
+/* Pushes a GC context; sets Function and ClipMask. */
+void
+jwxyz_gl_set_gc (Display *dpy, GC gc)
+{
+ int function;
+ Bool alpha_allowed_p;
+ GLuint clip_mask;
+
+ // GC is NULL for XClearArea and XClearWindow.
+ if (gc) {
+ function = gc->gcv.function;
+ alpha_allowed_p = gc->gcv.alpha_allowed_p || gc->clip_mask;
+ clip_mask = gc->clip_mask;
+ } else {
+ function = GXcopy;
+ alpha_allowed_p = False;
+ clip_mask = 0;
+ }
+
+ /* GL_COLOR_LOGIC_OP: OpenGL 1.1. */
+ if (function != dpy->gc_function) {
+ dpy->gc_function = function;
+ if (function != GXcopy) {
+ /* Fun fact: The glLogicOp opcode constants are the same as the X11 GX*
+ function constants | GL_CLEAR.
+ */
+ glEnable (GL_COLOR_LOGIC_OP);
+ glLogicOp (gc->gcv.function | GL_CLEAR);
+ } else {
+ glDisable (GL_COLOR_LOGIC_OP);
+ }
+ }
+
+ /* Cocoa uses add/subtract/difference blending in place of logical ops.
+ It looks nice, but implementing difference blending in OpenGL appears to
+ require GL_KHR_blend_equation_advanced, and support for this is not
+ widespread.
+ */
+
+ dpy->gc_alpha_allowed_p = alpha_allowed_p;
+ if (alpha_allowed_p || clip_mask) {
+ // TODO: Maybe move glBlendFunc to XCreatePixmap?
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_BLEND);
+ } else {
+ glDisable (GL_BLEND);
+ }
+
+ /* Texture units:
+ GL_TEXTURE0: Texture for XPutImage/XCopyArea (if applicable)
+ GL_TEXTURE1: Texture for clip masks (if applicable)
+ */
+ dpy->gc_clip_mask = clip_mask;
+
+ glActiveTexture (GL_TEXTURE1);
+ if (clip_mask) {
+ glEnable (dpy->gl_texture_target);
+ glBindTexture (dpy->gl_texture_target, gc->clip_mask);
+
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
+ alpha_allowed_p ? GL_MODULATE : GL_REPLACE);
+
+ glMatrixMode (GL_TEXTURE);
+ glLoadIdentity ();
+
+ unsigned
+ tex_w = gc->clip_mask_width + 2, tex_h = gc->clip_mask_height + 2;
+ tex_size (dpy, &tex_w, &tex_h);
+
+# ifndef HAVE_JWZGLES
+ if (dpy->gl_texture_target == GL_TEXTURE_RECTANGLE_EXT)
+ {
+ glScalef (1, -1, 1);
+ }
+ else
+# endif
+ {
+ glScalef (1.0f / tex_w, -1.0f / tex_h, 1);
+ }
+
+ glTranslatef (1 - gc->gcv.clip_x_origin,
+ 1 - gc->gcv.clip_y_origin - (int)gc->clip_mask_height - 2,
+ 0);
+ } else {
+ glDisable (dpy->gl_texture_target);
+ }
+ glActiveTexture (GL_TEXTURE0);
+}
+
+
+static void
+set_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color)
+{
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ unsigned int depth;
+
+ if (gc) {
+ depth = gc->depth;
+
+ switch (gc->gcv.function) {
+ case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
+ case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
+ }
+ } else {
+ depth = visual_depth (NULL, NULL);
+ }
+
+ set_color (dpy, color, depth, gc ? gc->gcv.alpha_allowed_p : False);
+}
+
+/* Pushes a GC context; sets color to the foreground color.
+ */
+static void
+set_fg_gc (Display *dpy, Drawable d, GC gc)
+{
+ set_color_gc (dpy, d, gc, gc->gcv.foreground);
+}
+
+static void
+next_point(short *v, XPoint p, int mode)
+{
+ switch (mode) {
+ case CoordModeOrigin:
+ v[0] = p.x;
+ v[1] = p.y;
+ break;
+ case CoordModePrevious:
+ v[0] += p.x;
+ v[1] += p.y;
+ break;
+ default:
+ Assert (False, "next_point: bad mode");
+ break;
+ }
+}
+
+static int
+DrawPoints (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int count, int mode)
+{
+ short v[2] = {0, 0};
+
+ // TODO: XPoints can be fed directly to OpenGL.
+ GLshort *gl_points = enqueue (dpy, d, gc, GL_POINTS, count,
+ gc->gcv.foreground); // TODO: enqueue returns NULL.
+ for (unsigned i = 0; i < count; i++) {
+ next_point (v, points[i], mode);
+ gl_points[2 * i] = v[0];
+ gl_points[2 * i + 1] = v[1];
+ }
+
+ return 0;
+}
+
+
+static GLint
+texture_internalformat (Display *dpy)
+{
+#ifdef HAVE_JWZGLES
+ return dpy->pixel_format;
+#else
+ return GL_RGBA;
+#endif
+}
+
+static GLenum
+gl_pixel_type (const Display *dpy)
+{
+ return dpy->pixel_type;
+}
+
+static void
+clear_texture (Display *dpy)
+{
+ glTexImage2D (dpy->gl_texture_target, 0, texture_internalformat(dpy), 0, 0,
+ 0, dpy->pixel_format, gl_pixel_type (dpy), NULL);
+}
+
+
+static void
+vertex_pointer (Display *dpy, GLenum type, GLsizei stride,
+ const void *pointer)
+{
+ glVertexPointer(2, type, stride, pointer);
+ if (dpy->gc_clip_mask) {
+ glClientActiveTexture (GL_TEXTURE1);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ glTexCoordPointer (2, type, stride, pointer);
+ glClientActiveTexture (GL_TEXTURE0);
+ }
+}
+
+
+void
+jwxyz_gl_flush (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ if (!dpy->queue_size)
+ return;
+
+ // jwxyz_bind_drawable() and jwxyz_gl_set_gc() is called in enqueue().
+
+ glEnableClientState (GL_COLOR_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ // TODO: Use glColor instead of glColorPointer if there's just one color.
+ // TODO: Does OpenGL use both GL_COLOR_ARRAY and glColor at the same time?
+ // (Probably not.)
+ glColor4f (1, 1, 1, 1);
+
+ Bool shifted = dpy->queue_mode == GL_POINTS || dpy->queue_mode == GL_LINES;
+ if (shifted) {
+ glMatrixMode (GL_MODELVIEW);
+ glTranslatef (0.5, 0.5, 0);
+ }
+
+ glColorPointer (4, GL_UNSIGNED_BYTE, 0, dpy->queue_color);
+ vertex_pointer (dpy,
+ dpy->queue_mode == GL_TRIANGLE_STRIP ? GL_FLOAT : GL_SHORT,
+ 0, dpy->queue_vertex);
+ glDrawArrays (dpy->queue_mode, 0, dpy->queue_size);
+
+ // TODO: This is right, right?
+ if (dpy->queue_mode == GL_LINES && dpy->queue_line_cap) {
+ Assert (!(dpy->queue_size % 2), "bad count for GL_LINES");
+ glColorPointer (4, GL_UNSIGNED_BYTE, sizeof(GLubyte) * 8,
+ dpy->queue_color);
+ vertex_pointer (dpy, GL_SHORT, sizeof(GLshort) * 4,
+ (GLshort *)dpy->queue_vertex + 2);
+ glDrawArrays (GL_POINTS, 0, dpy->queue_size / 2);
+ }
+
+ if (shifted)
+ glLoadIdentity ();
+
+ glDisableClientState (GL_COLOR_ARRAY);
+ glDisableClientState (GL_VERTEX_ARRAY);
+
+ dpy->queue_size = 0;
+}
+
+
+void
+jwxyz_gl_copy_area_read_tex_image (Display *dpy, unsigned src_height,
+ int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+# if defined HAVE_COCOA && !defined USE_IPHONE
+ /* TODO: Does this help? */
+ /* glFinish(); */
+# endif
+
+ /* TODO: Fix TestX11 + mode_preserve with this one. */
+
+ unsigned tex_w = width, tex_h = height;
+ if (!dpy->gl_texture_npot_p) {
+ tex_w = to_pow2(tex_w);
+ tex_h = to_pow2(tex_h);
+ }
+
+ GLint internalformat = texture_internalformat(dpy);
+
+ /* TODO: This probably shouldn't always be texture_rgba. */
+ glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);
+
+ if (tex_w == width && tex_h == height) {
+ glCopyTexImage2D (dpy->gl_texture_target, 0, internalformat,
+ src_x, src_height - src_y - height, width, height, 0);
+ } else {
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
+ 0, dpy->pixel_format, gl_pixel_type(dpy), NULL);
+ glCopyTexSubImage2D (dpy->gl_texture_target, 0, 0, 0,
+ src_x, src_height - src_y - height, width, height);
+ }
+}
+
+void
+jwxyz_gl_copy_area_write_tex_image (Display *dpy, GC gc, int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+ jwxyz_gl_set_gc (dpy, gc);
+
+ /* TODO: Copy-pasted from read_tex_image. */
+ unsigned tex_w = width, tex_h = height;
+ if (!dpy->gl_texture_npot_p) {
+ tex_w = to_pow2(tex_w);
+ tex_h = to_pow2(tex_h);
+ }
+
+ /* Must match what's in jwxyz_gl_copy_area_read_tex_image. */
+ glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);
+
+ jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
+ 0, 0, gc->depth, width, height, dst_x, dst_y, False);
+
+ clear_texture (dpy);
+}
+
+
+void
+jwxyz_gl_draw_image (Display *dpy, GC gc, GLenum gl_texture_target,
+ unsigned int tex_w, unsigned int tex_h,
+ int src_x, int src_y, int src_depth,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y, Bool flip_y)
+{
+ if (!gc || src_depth == gc->depth) {
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ } else {
+ Assert (src_depth == 1 && gc->depth == 32,
+ "jwxyz_gl_draw_image: bad depths");
+
+ set_color (dpy, gc->gcv.background, gc->depth, gc->gcv.alpha_allowed_p);
+
+ GLfloat rgba[4];
+ query_color (dpy, gc->gcv.foreground, gc->depth, gc->gcv.alpha_allowed_p,
+ rgba);
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
+ glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, rgba);
+ }
+
+ glEnable (gl_texture_target);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+
+ Assert (!glIsEnabled (GL_COLOR_ARRAY), "glIsEnabled (GL_COLOR_ARRAY)");
+ Assert (!glIsEnabled (GL_NORMAL_ARRAY), "glIsEnabled (GL_NORMAL_ARRAY)");
+
+ /* TODO: EXT_draw_texture or whatever it's called. */
+ GLfloat vertices[4][2] = {
+ {dst_x, dst_y},
+ {dst_x, dst_y + height},
+ {dst_x + width, dst_y + height},
+ {dst_x + width, dst_y}
+ };
+
+ GLfloat
+ tex_x0 = src_x, tex_y0 = src_y,
+ tex_x1 = src_x + width, tex_y1 = src_y;
+
+ if (flip_y)
+ tex_y1 += height;
+ else
+ tex_y0 += height;
+
+# ifndef HAVE_JWZGLES
+ if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
+# endif
+ {
+ GLfloat mx = 1.0f / tex_w, my = 1.0f / tex_h;
+ tex_x0 *= mx;
+ tex_y0 *= my;
+ tex_x1 *= mx;
+ tex_y1 *= my;
+ }
+
+ GLfloat tex_coords[4][2] = {
+ {tex_x0, tex_y0},
+ {tex_x0, tex_y1},
+ {tex_x1, tex_y1},
+ {tex_x1, tex_y0}
+ };
+
+ vertex_pointer (dpy, GL_FLOAT, 0, vertices);
+ glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
+
+//clear_texture();
+ glDisable (gl_texture_target);
+}
+
+#if 0
+void
+jwxyz_gl_copy_area_read_pixels (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)
+{
+ XImage *img = XGetImage (dpy, src, src_x, src_y, width, height, ~0, ZPixmap);
+ XPutImage (dpy, dst, gc, img, 0, 0, dst_x, dst_y, width, height);
+ XDestroyImage (img);
+}
+#endif
+
+
+static int
+DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
+ int mode)
+{
+ set_fg_gc (dpy, d, gc);
+
+ /* TODO: Thick lines
+ * Zero-length line segments
+ * Paths with zero length total (Remember line width, cap style.)
+ * Closed loops
+ */
+
+ if (!count)
+ return 0;
+
+ GLshort *vertices = malloc(2 * sizeof(GLshort) * count); // TODO malloc NULL sigh
+
+ glMatrixMode (GL_MODELVIEW);
+ glTranslatef (0.5f, 0.5f, 0);
+
+ short p[2] = {0, 0};
+ for (unsigned i = 0; i < count; i++) {
+ next_point (p, points[i], mode);
+ vertices[2 * i] = p[0];
+ vertices[2 * i + 1] = p[1];
+ }
+
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ vertex_pointer (dpy, GL_SHORT, 0, vertices);
+ glDrawArrays (GL_LINE_STRIP, 0, count);
+
+ free (vertices);
+
+ if (gc->gcv.cap_style != CapNotLast) {
+ // TODO: How does this look with multisampling?
+ // TODO: Disable me for closed loops.
+ vertex_pointer (dpy, GL_SHORT, 0, p);
+ glDrawArrays (GL_POINTS, 0, 1);
+ }
+
+ glLoadIdentity ();
+
+ return 0;
+}
+
+
+// Turn line segment into parallelogram based on line_width
+//
+// TODO: Fix epicycle hack with large thickness, and truchet line segment ends
+//
+static void
+drawThickLine (Display *dpy, Drawable d, GC gc, int line_width,
+ XSegment *segments)
+{
+ double dx, dy, di, m, angle;
+ int sx1, sx2, sy1, sy2;
+
+ sx1 = segments->x1;
+ sy1 = segments->y1;
+ sx2 = segments->x2;
+ sy2 = segments->y2;
+
+ dx = sx1 - sx2;
+ dy = sy1 - sy2;
+ di = sqrt(dx * dx + dy * dy);
+ dx = dx / di;
+ dy = dy / di;
+ m = dy / dx;
+
+ angle = atan(m);
+
+ float sn = sin(angle);
+ float cs = cos(angle);
+ float line_width_f = (float) line_width;
+
+ float wsn = line_width_f * (sn/2);
+ float csn = line_width_f * (cs/2);
+
+ float x3 = sx1 - wsn;
+ float y3 = sy1 + csn;
+ float x4 = sx1 + wsn;
+ float y4 = sy1 - csn;
+
+ float x5 = sx2 - wsn;
+ float y5 = sy2 + csn;
+ float x6 = sx2 + wsn;
+ float y6 = sy2 - csn;
+
+ GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4,
+ gc->gcv.foreground);
+ coords[0] = x3;
+ coords[1] = y3;
+ coords[2] = x4;
+ coords[3] = y4;
+ coords[4] = x5;
+ coords[5] = y5;
+ coords[6] = x6;
+ coords[7] = y6;
+ finish_triangle_strip (dpy, coords);
+}
+
+
+static int
+DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
+{
+ /* TODO: Caps on thick lines. */
+ /* Thin lines <= 1px are offset by +0.5; thick lines are not. */
+
+ if (count == 1 && gc->gcv.line_width > 1) {
+ drawThickLine (dpy, d, gc, gc->gcv.line_width, segments);
+ }
+ else {
+ if (dpy->queue_line_cap != (gc->gcv.cap_style != CapNotLast))
+ jwxyz_gl_flush (dpy);
+ dpy->queue_line_cap = gc->gcv.cap_style != CapNotLast;
+
+ // TODO: Static assert here.
+ Assert (sizeof(XSegment) == sizeof(short) * 4 &&
+ sizeof(GLshort) == sizeof(short) &&
+ offsetof(XSegment, x1) == 0 &&
+ offsetof(XSegment, x2) == 4,
+ "XDrawSegments: Data alignment mix-up.");
+
+ memcpy (enqueue(dpy, d, gc, GL_LINES, count * 2, gc->gcv.foreground),
+ segments, count * sizeof(XSegment));
+ }
+
+ return 0;
+}
+
+
+static int
+ClearWindow (Display *dpy, Window win)
+{
+ Assert (win == dpy->main_window, "not a window");
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, win, win);
+
+ GLfloat color[4];
+ JWXYZ_QUERY_COLOR (dpy, dpy->window_background, 1.0f, color);
+
+ glClearColor (color[0], color[1], color[2], 1);
+ glClear (GL_COLOR_BUFFER_BIT);
+ return True;
+}
+
+static unsigned long *
+window_background (Display *dpy)
+{
+ return &dpy->window_background;
+}
+
+static void
+fill_rects (Display *dpy, Drawable d, GC gc,
+ const XRectangle *rectangles, unsigned long nrectangles,
+ unsigned long pixel)
+{
+ for (unsigned long i = 0; i != nrectangles; ++i) {
+ const XRectangle *r = &rectangles[i];
+ GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4, pixel);
+ coords[0] = r->x;
+ coords[1] = r->y;
+ coords[2] = r->x;
+ coords[3] = r->y + r->height;
+ coords[4] = r->x + r->width;
+ coords[5] = r->y;
+ coords[6] = r->x + r->width;
+ coords[7] = r->y + r->height;
+ finish_triangle_strip (dpy, coords);
+ }
+}
+
+
+static int
+FillPolygon (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int npoints, int shape, int mode)
+{
+ set_fg_gc(dpy, d, gc);
+
+ // TODO: Re-implement the GLU tesselation functions.
+
+ /* Complex: Pedal, and for some reason Attraction, Mountain, Qix, SpeedMine, Starfish
+ * Nonconvex: Goop, Pacman, Rocks, Speedmine
+ *
+ * We currently do Nonconvex with the simple-to-implement ear clipping
+ * algorithm, but in the future we can replace that with an algorithm
+ * with slower big-O growth
+ *
+ */
+
+
+ // TODO: Feed vertices straight to OpenGL for CoordModeOrigin.
+
+ if (shape == Convex) {
+
+ GLshort *vertices = malloc(npoints * sizeof(GLshort) * 2); // TODO: Oh look, another unchecked malloc.
+ short v[2] = {0, 0};
+
+ for (unsigned i = 0; i < npoints; i++) {
+ next_point(v, points[i], mode);
+ vertices[2 * i] = v[0];
+ vertices[2 * i + 1] = v[1];
+ }
+
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ vertex_pointer (dpy, GL_SHORT, 0, vertices);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, npoints);
+
+ free(vertices);
+
+ } else if (shape == Nonconvex) {
+
+ // TODO: assert that x,y of first and last point match, as that is assumed
+
+ linked_point *root;
+ root = (linked_point *) malloc( sizeof(linked_point) );
+ set_points_list(points,npoints,root);
+ traverse_points_list(dpy, root);
+
+ } else {
+ Assert((shape == Convex || shape == Nonconvex),
+ "XFillPolygon: (TODO) Unimplemented shape");
+ }
+
+ return 0;
+}
+
+#define radians(DEG) ((DEG) * M_PI / 180.0)
+#define degrees(RAD) ((RAD) * 180.0 / M_PI)
+
+static void
+arc_xy(GLfloat *p, double cx, double cy, double w2, double h2, double theta)
+{
+ p[0] = cos(theta) * w2 + cx;
+ p[1] = -sin(theta) * h2 + cy;
+}
+
+static unsigned
+mod_neg(int a, unsigned b)
+{
+ /* Normal modulus is implementation defined for negative numbers. This is
+ * well-defined such that the repeating pattern for a >= 0 is maintained for
+ * a < 0. */
+ return a < 0 ? (b - 1) - (-(a + 1) % b) : a % b;
+}
+
+/* TODO: Fill in arcs with line width > 1 */
+static int
+draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ int gglw = gc->gcv.line_width;
+
+ if (fill_p || gglw <= 1) {
+ draw_arc_gl (dpy, d, gc, x, y, width, height, angle1, angle2, fill_p);
+ }
+ else {
+ int w1, w2, h1, h2, gglwh;
+ w1 = width + gglw;
+ h1 = height + gglw;
+ h2 = height - gglw;
+ w2 = width - gglw;
+ gglwh = gglw / 2;
+ int x1 = x - gglwh;
+ int x2 = x + gglwh;
+ int y1 = y - gglwh;
+ int y2 = y + gglwh;
+ //draw_arc_gl (dpy, d, gc, x, y, width, height, angle1, angle2, fill_p);
+ draw_arc_gl (dpy, d, gc, x1, y1, w1, h1, angle1, angle2, fill_p);
+ draw_arc_gl (dpy, d, gc, x2, y2, w2, h2, angle1, angle2, fill_p);
+ }
+ return 0;
+}
+
+
+int
+draw_arc_gl (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ set_fg_gc(dpy, d, gc);
+
+ /* Let's say the number of line segments needed to make a convincing circle is
+ 4*sqrt(radius). (But these arcs aren't necessarily circular arcs...) */
+
+ double w2 = width * 0.5f, h2 = height * 0.5f;
+ double a, b; /* Semi-major/minor axes. */
+ if(w2 > h2) {
+ a = w2;
+ b = h2;
+ } else {
+ a = h2;
+ b = w2;
+ }
+
+ const double two_pi = 2 * M_PI;
+
+ double amb = a - b, apb = a + b;
+ double h = (amb * amb) / (apb * apb);
+ // TODO: Math cleanup.
+ double C_approx = M_PI * apb * (1 + 3 * h / (10 + sqrtf(4 - 3 * h)));
+ double segments_f = 4 * sqrtf(C_approx / (2 * M_PI));
+
+ // TODO: Explain how drawing works what with the points of overlapping arcs
+ // matching up.
+
+#if 1
+ unsigned segments_360 = segments_f;
+
+ /* TODO: angle2 == 0. This is a tilted square with CapSquare. */
+ /* TODO: color, thick lines, CapNotLast for thin lines */
+ /* TODO: Transformations. */
+
+ double segment_angle = two_pi / segments_360;
+
+ const unsigned deg64 = 360 * 64;
+ const double rad_from_deg64 = two_pi / deg64;
+
+ if (angle2 < 0) {
+ angle1 += angle2;
+ angle2 = -angle2;
+ }
+
+ angle1 = mod_neg(angle1, deg64); // TODO: Is this OK? Consider negative numbers.
+
+ if (angle2 > deg64)
+ angle2 = deg64; // TODO: Handle circles special.
+
+ double
+ angle1_f = angle1 * rad_from_deg64,
+ angle2_f = angle2 * rad_from_deg64;
+
+ if (angle2_f > two_pi) // TODO: Move this up.
+ angle2_f = two_pi;
+
+ double segment1_angle_part = fmodf(angle1_f, segment_angle);
+
+ unsigned segment1 = ((angle1_f - segment1_angle_part) / segment_angle) + 1.5;
+
+ double angle_2r = angle2_f - segment1_angle_part;
+ unsigned segments = angle_2r / segment_angle;
+
+ GLfloat cx = x + w2, cy = y + h2;
+
+ GLfloat *data = malloc((segments + 3) * sizeof(GLfloat) * 2); // TODO: Check result.
+
+ GLfloat *data_ptr = data;
+ if (fill_p) {
+ data_ptr[0] = cx;
+ data_ptr[1] = cy;
+ data_ptr += 2;
+ }
+
+ arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
+ data_ptr += 2;
+
+ for (unsigned s = 0; s != segments; ++s) {
+ // TODO: Make sure values of theta for the following arc_xy call are between
+ // angle1_f and angle1_f + angle2_f.
+ arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + s) * segment_angle);
+ data_ptr += 2;
+ }
+
+ arc_xy (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f);
+ data_ptr += 2;
+
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+
+ vertex_pointer (dpy, GL_FLOAT, 0, data);
+ glDrawArrays (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP,
+ 0,
+ (GLsizei)((data_ptr - data) / 2));
+
+ free(data);
+
+#endif
+
+#if 0
+ unsigned segments = segments_f * (fabs(angle2) / (360 * 64));
+
+ glBegin (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP);
+
+ if (fill_p /* && gc->gcv.arc_mode == ArcPieSlice */)
+ glVertex2f (cx, cy);
+
+ /* TODO: This should fix the middle points of the arc so that the starting and ending points are OK. */
+
+ float to_radians = 2 * M_PI / (360 * 64);
+ float theta = angle1 * to_radians, d_theta = angle2 * to_radians / segments;
+
+ for (unsigned s = 0; s != segments + 1; ++s) /* TODO: This is the right number of segments, yes? */
+ {
+ glVertex2f(cos(theta) * w2 + cx, -sin(theta) * h2 + cy);
+ theta += d_theta;
+ }
+
+ glEnd ();
+#endif
+
+ return 0;
+}
+
+
+static XGCValues *
+gc_gcv (GC gc)
+{
+ return &gc->gcv;
+}
+
+
+static unsigned int
+gc_depth (GC gc)
+{
+ return gc->depth;
+}
+
+
+static GC
+CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
+{
+ struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
+ gc->depth = jwxyz_drawable_depth (d);
+
+ jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
+ XChangeGC (dpy, gc, mask, xgcv);
+ return gc;
+}
+
+
+static void
+free_clip_mask (Display *dpy, GC gc)
+{
+ if (gc->gcv.clip_mask) {
+ if (dpy->gc_clip_mask == gc->clip_mask) {
+ jwxyz_gl_flush (dpy);
+ dpy->gc_clip_mask = 0;
+ }
+ glDeleteTextures (1, &gc->clip_mask);
+ }
+}
+
+
+static int
+FreeGC (Display *dpy, GC gc)
+{
+ if (gc->gcv.font)
+ XUnloadFont (dpy, gc->gcv.font);
+
+ free_clip_mask (dpy, gc);
+ free (gc);
+ return 0;
+}
+
+
+static int
+PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h)
+{
+ jwxyz_assert_display (dpy);
+
+ const XRectangle *wr = jwxyz_frame (d);
+
+ Assert (gc, "no GC");
+ Assert ((w < 65535), "improbably large width");
+ Assert ((h < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
+ Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
+
+ // Clip width and height to the bounds of the Drawable
+ //
+ if (dest_x + w > wr->width) {
+ if (dest_x > wr->width)
+ return 0;
+ w = wr->width - dest_x;
+ }
+ if (dest_y + h > wr->height) {
+ if (dest_y > wr->height)
+ return 0;
+ h = wr->height - dest_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ // Clip width and height to the bounds of the XImage
+ //
+ if (src_x + w > ximage->width) {
+ if (src_x > ximage->width)
+ return 0;
+ w = ximage->width - src_x;
+ }
+ if (src_y + h > ximage->height) {
+ if (src_y > ximage->height)
+ return 0;
+ h = ximage->height - src_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ /* Assert (d->win */
+
+ if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
+ return 0;
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ int bpl = ximage->bytes_per_line;
+ int bpp = ximage->bits_per_pixel;
+
+ char *tex_data;
+ unsigned src_w;
+ GLint tex_internalformat;
+ GLenum tex_format, tex_type;
+ unsigned tex_index;
+
+ if (bpp == 32) {
+ tex_data = ximage->data + src_y * bpl + (src_x * 4);
+
+ jwxyz_assert_display(dpy);
+
+ /* There probably won't be any hacks that do this, but... */
+ Assert (!(bpl % 4), "XPutImage: bytes_per_line not divisible by four.");
+
+ tex_internalformat = texture_internalformat(dpy);
+ tex_format = dpy->pixel_format;
+ tex_type = gl_pixel_type(dpy);
+ tex_index = texture_rgba;
+
+ /* GL_UNPACK_ROW_LENGTH is not allowed to be negative. (sigh) */
+# ifndef HAVE_JWZGLES
+ src_w = w;
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, src_w);
+# else
+ src_w = bpl / 4;
+# endif
+
+ // glPixelStorei (GL_UNPACK_ALIGNMENT, 4); // Probably unnecessary.
+ } else {
+ Assert (bpp == 1, "expected 1 or 32 bpp");
+ Assert ((src_x % 8) == 0,
+ "XPutImage with non-byte-aligned 1bpp X offset not implemented");
+
+ const char *src_data = ximage->data + src_y * bpl + (src_x / 8);
+ unsigned w8 = (w + 7) / 8;
+
+ src_w = w8 * 8;
+
+ tex_data = malloc(src_w * h);
+
+#if 0
+ {
+ char s[10240];
+ int x, y, o;
+ Log("#PI ---------- %d %d %08lx %08lx",
+ jwxyz_drawable_depth(d), ximage->depth,
+ (unsigned long)d, (unsigned long)ximage);
+ for (y = 0; y < ximage->height; y++) {
+ o = 0;
+ for (x = 0; x < ximage->width; x++) {
+ unsigned long b = XGetPixel(ximage, x, y);
+ s[o++] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[o] = 0;
+ Log("#PI [%s]",s);
+ }
+ Log("# PI ----------");
+ }
+#endif
+ uint32_t *data_out = (uint32_t *)tex_data;
+ for(unsigned y = h; y; --y) {
+ for(unsigned x = 0; x != w8; ++x) {
+ // TODO: Does big endian work here?
+ uint8_t byte = src_data[x];
+ uint32_t word = byte;
+ word = (word & 0x3) | ((word & 0xc) << 14);
+ word = (word & 0x00010001) | ((word & 0x00020002) << 7);
+ data_out[x << 1] = (word << 8) - word;
+
+ word = byte >> 4;
+ word = (word & 0x3) | ((word & 0xc) << 14);
+ word = (word & 0x00010001) | ((word & 0x00020002) << 7);
+ data_out[(x << 1) | 1] = (word << 8) - word;
+ }
+ src_data += bpl;
+ data_out += src_w / 4;
+ }
+#if 0
+ {
+ char s[10240];
+ int x, y, o;
+ Log("#P2 ----------");
+ for (y = 0; y < ximage->height; y++) {
+ o = 0;
+ for (x = 0; x < ximage->width; x++) {
+ unsigned long b = ((uint8_t *)tex_data)[y * w + x];
+ s[o++] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[o] = 0;
+ Log("#P2 [%s]",s);
+ }
+ Log("# P2 ----------");
+ }
+#endif
+
+ tex_internalformat = GL_LUMINANCE;
+ tex_format = GL_LUMINANCE;
+ tex_type = GL_UNSIGNED_BYTE;
+ tex_index = texture_mono;
+
+ // glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+ }
+
+# if 1 // defined HAVE_JWZGLES
+ // Regular OpenGL uses GL_TEXTURE_RECTANGLE_EXT in place of GL_TEXTURE_2D.
+ // TODO: Make use of OES_draw_texture.
+
+ unsigned tex_w = src_w, tex_h = h;
+ glBindTexture (dpy->gl_texture_target, dpy->textures[tex_index]);
+
+ // A fun project: reimplement xshm.c by means of a PBO using
+ // GL_MAP_UNSYNCHRONIZED_BIT.
+
+ tex_image (dpy, tex_internalformat, &tex_w, &tex_h, tex_format, tex_type,
+ tex_data);
+
+ if (bpp == 1)
+ free(tex_data);
+
+ jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
+ 0, 0, bpp, w, h, dest_x, dest_y, True);
+# else
+ glRasterPos2i (dest_x, dest_y);
+ glPixelZoom (1, -1);
+ jwxyz_assert_display (dpy);
+ glDrawPixels (w, h, dpy->pixel_format, gl_pixel_type(dpy), data);
+# endif
+
+ jwxyz_assert_gl ();
+
+ return 0;
+}
+
+/* At the moment only XGetImage and get_xshm_image use XGetSubImage. */
+/* #### Twang calls XGetImage on the window intending to get a
+ buffer full of black. This is returning a buffer full of white
+ instead of black for some reason. */
+static XImage *
+GetSubImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format,
+ XImage *dest_image, int dest_x, int dest_y)
+{
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((x < 65535 && x > -65535), "improbably large x");
+ Assert ((y < 65535 && y > -65535), "improbably large y");
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+
+ // TODO: What if this reads off the edge? What is supposed to happen?
+
+ {
+ // In case the caller tries to write off the edge.
+ int
+ max_width = dest_image->width - dest_x,
+ max_height = dest_image->height - dest_y;
+
+ if (width > max_width) {
+ width = max_width;
+ }
+
+ if (height > max_height) {
+ height = max_height;
+ }
+ }
+
+ Assert (jwxyz_drawable_depth (d) == dest_image->depth, "XGetSubImage: depth mismatch");
+
+ if (dest_image->depth == visual_depth (NULL, NULL)) {
+ Assert (!(dest_image->bytes_per_line % 4), "XGetSubImage: bytes_per_line not divisible by 4");
+ unsigned pixels_per_line = dest_image->bytes_per_line / 4;
+#ifdef HAVE_JWZGLES
+ Assert (pixels_per_line == width, "XGetSubImage: (TODO) pixels_per_line != width");
+#else
+ glPixelStorei (GL_PACK_ROW_LENGTH, pixels_per_line);
+#endif
+ glPixelStorei (GL_PACK_ALIGNMENT, 4);
+
+ uint32_t *dest_data = (uint32_t *)dest_image->data + pixels_per_line * dest_y + dest_x;
+
+ glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
+ dpy->pixel_format, gl_pixel_type(dpy), dest_data);
+
+ /* Flip this upside down. :( */
+ uint32_t *top = dest_data;
+ uint32_t *bottom = dest_data + pixels_per_line * (height - 1);
+ for (unsigned y = height / 2; y; --y) {
+ for (unsigned x = 0; x != width; ++x) {
+ uint32_t px = top[x];
+ top[x] = bottom[x];
+ bottom[x] = px;
+ }
+ top += pixels_per_line;
+ bottom -= pixels_per_line;
+ }
+ } else {
+
+ uint32_t *rgba_image = malloc(width * height * 4);
+ Assert (rgba_image, "not enough memory");
+
+ // Must be GL_RGBA; GL_RED isn't available.
+ glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
+ GL_RGBA, GL_UNSIGNED_BYTE, rgba_image);
+
+ Assert (!(dest_x % 8), "XGetSubImage: dest_x not byte-aligned");
+ uint8_t *top =
+ (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y
+ + dest_x / 8;
+#if 0
+ {
+ char s[10240];
+ Log("#GI ---------- %d %d %d x %d %08lx",
+ jwxyz_drawable_depth(d), dest_image->depth,
+ width, height,
+ (unsigned long) d);
+ for (int y = 0; y < height; y++) {
+ int x;
+ for (x = 0; x < width; x++) {
+ unsigned long b = rgba_image[(height-y-1) * width + x];
+ s[x] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[x] = 0;
+ Log("#GI [%s]",s);
+ }
+ Log("#GI ----------");
+ }
+#endif
+ const uint32_t *bottom = rgba_image + width * (height - 1);
+ for (unsigned y = height; y; --y) {
+ memset (top, 0, width / 8);
+ for (unsigned x = 0; x != width; ++x) {
+ if (bottom[x] & 0x80)
+ top[x >> 3] |= (1 << (x & 7));
+ }
+ top += dest_image->bytes_per_line;
+ bottom -= width;
+ }
+
+ free (rgba_image);
+ }
+
+ return dest_image;
+}
+
+
+static int
+SetClipMask (Display *dpy, GC gc, Pixmap m)
+{
+ Assert (m != dpy->main_window, "not a pixmap");
+
+ free_clip_mask (dpy, gc);
+
+ if (!m) {
+ gc->clip_mask = 0;
+ return 0;
+ }
+
+ Assert (jwxyz_drawable_depth (m) == 1, "wrong depth for clip mask");
+
+ const XRectangle *frame = jwxyz_frame (m);
+ gc->clip_mask_width = frame->width;
+ gc->clip_mask_height = frame->height;
+
+ /* Apparently GL_ALPHA isn't a valid format for a texture used in an FBO:
+
+ (86) Are any one- or two- component formats color-renderable?
+
+ Presently none of the one- or two- component texture formats
+ defined in unextended OpenGL is color-renderable. The R
+ and RG float formats defined by the NV_float_buffer
+ extension are color-renderable.
+
+ Although an early draft of the FBO specification permitted
+ rendering into alpha, luminance, and intensity formats, this
+ this capability was pulled when it was realized that it was
+ under-specified exactly how rendering into these formats
+ would work. (specifically, how R/G/B/A map to I/L/A)
+
+ <https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_framebuffer_object.txt>
+
+ ...Therefore, 1-bit drawables get to have wasted color channels.
+ Currently, R=G=B=grey, and the alpha channel is unused.
+ */
+
+ /* There doesn't seem to be any way to move what's on one of the color
+ channels to the alpha channel in GLES 1.1 without leaving the GPU.
+ */
+
+ /* GLES 1.1 only supports GL_CLAMP_TO_EDGE or GL_REPEAT for
+ GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, so to prevent drawing outside
+ the clip mask pixmap, there's two options:
+ 1. Use glScissor.
+ 2. Add a black border.
+
+ Option #2 is in use here.
+
+ The following converts in-place an RGBA image to alpha-only image with a
+ 1px black border.
+ */
+
+ // Prefix/suffix: 1 row+1 pixel, rounded to nearest int32.
+ size_t pad0 = frame->width + 3, pad = (pad0 + 3) & ~3;
+ uint8_t *data = malloc(2 * pad + frame->width * frame->height * 4);
+
+ uint8_t *rgba = data + pad;
+ uint8_t *alpha = rgba;
+ uint8_t *alpha0 = alpha - pad0;
+ memset (alpha0, 0, pad0); // Top row.
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, m);
+ glReadPixels (0, 0, frame->width, frame->height, GL_RGBA, GL_UNSIGNED_BYTE,
+ rgba);
+
+ for (unsigned y = 0; y != frame->height; ++y) {
+ for (unsigned x = 0; x != frame->width; ++x)
+ alpha[x] = rgba[x * 4];
+
+ rgba += frame->width * 4;
+
+ alpha += frame->width;
+ alpha[0] = 0;
+ alpha[1] = 0;
+ alpha += 2;
+ }
+
+ alpha -= 2;
+ memset (alpha, 0, pad0); // Bottom row.
+
+ glGenTextures (1, &gc->clip_mask);
+ tex_parameters (dpy, gc->clip_mask);
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+
+ unsigned tex_w = frame->width + 2, tex_h = frame->height + 2;
+ tex_image (dpy, GL_ALPHA, &tex_w, &tex_h, GL_ALPHA, GL_UNSIGNED_BYTE,
+ alpha0);
+
+ free(data);
+
+ return 0;
+}
+
+static int
+SetClipOrigin (Display *dpy, GC gc, int x, int y)
+{
+ gc->gcv.clip_x_origin = x;
+ gc->gcv.clip_y_origin = y;
+ return 0;
+}
+
+void set_points_list(XPoint *points, int npoints, linked_point *root)
+{
+ linked_point *current;
+
+ current = root;
+ for (int i = 0; i < npoints - 2 ; i++) {
+ current->x = points[i].x;
+ current->y = points[i].y;
+ current->next = (linked_point *) malloc(sizeof(linked_point));
+ current = current->next;
+ }
+ current->x = points[npoints-2].x;
+ current->y = points[npoints-2].y;
+ current->next = root;
+}
+
+
+double compute_edge_length(linked_point * a, linked_point * b)
+{
+
+ int xdiff, ydiff, xsq, ysq, added;
+ double xy_add, edge_length;
+
+ xdiff = a->x - b->x;
+ ydiff = a->y - b->y;
+ xsq = xdiff * xdiff;
+ ysq = ydiff * ydiff;
+ added = xsq + ysq;
+ xy_add = (double) added;
+ edge_length = sqrt(xy_add);
+ return edge_length;
+}
+
+double get_angle(double a, double b, double c)
+{
+ double cos_a, i_cos_a;
+ cos_a = (((b * b) + (c * c)) - (a * a)) / (double) (2.0 * b * c);
+ i_cos_a = acos(cos_a);
+ return i_cos_a;
+}
+
+
+Bool is_same_slope(linked_point * a)
+{
+
+ int abx, bcx, aby, bcy, aa, bb;
+ linked_point *b;
+ linked_point *c;
+
+ b = a->next;
+ c = b->next;
+
+ // test if slopes are indefinite for both line segments
+ if (a->x == b->x) {
+ return b->x == c->x;
+ } else if (b->x == c->x) {
+ return False; // false, as ax/bx is not indefinite
+ }
+
+ abx = a->x - b->x;
+ bcx = b->x - c->x;
+ aby = a->y - b->y;
+ bcy = b->y - c->y;
+ aa = abx * bcy;
+ bb = bcx * aby;
+
+ return aa == bb;
+}
+
+void draw_three_vertices(Display *dpy, linked_point * a, Bool triangle)
+{
+
+ linked_point *b;
+ linked_point *c;
+ GLenum drawType;
+
+ b = a->next;
+ c = b->next;
+
+ GLfloat vertices[3][2] = {
+ {a->x, a->y},
+ {b->x, b->y},
+ {c->x, c->y}
+ };
+
+ if (triangle) {
+ drawType = GL_TRIANGLES;
+ } else {
+ drawType = GL_LINES;
+ }
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ vertex_pointer(dpy, GL_FLOAT, 0, vertices);
+ glDrawArrays(drawType, 0, 3);
+
+ free(b); // cut midpoint off from remaining polygon vertex list
+ a->next = c;
+}
+
+
+Bool is_an_ear(linked_point * a)
+{
+ double edge_ab, edge_bc, edge_ac;
+ double angle_a, angle_b, angle_c;
+ double my_pi;
+ linked_point *b, *c;
+
+ b = a->next;
+ c = b->next;
+ my_pi = (double) M_PI;
+
+ edge_ab = compute_edge_length(a, b);
+ edge_bc = compute_edge_length(b, c);
+ edge_ac = compute_edge_length(a, c);
+ angle_a = get_angle(edge_bc, edge_ab, edge_ac);
+ angle_b = get_angle(edge_ac, edge_ab, edge_bc);
+ angle_c = get_angle(edge_ab, edge_ac, edge_bc);
+
+ return angle_a < my_pi && angle_b < my_pi && angle_c < my_pi;
+}
+
+
+Bool is_three_point_loop(linked_point * head)
+{
+ return head->x == head->next->next->next->x
+ && head->y == head->next->next->next->y;
+}
+
+
+void traverse_points_list(Display *dpy, linked_point * root)
+{
+ linked_point *head;
+ head = root;
+
+ while (!is_three_point_loop(head)) {
+ if (is_an_ear(head)) {
+ draw_three_vertices(dpy, head, True);
+ } else if (is_same_slope(head)) {
+ draw_three_vertices(dpy, head, False);
+ } else {
+ head = head->next;
+ }
+ }
+
+ // handle final three vertices in polygon
+ if (is_an_ear(head)) {
+ draw_three_vertices(dpy, head, True);
+ } else if (is_same_slope(head)) {
+ draw_three_vertices(dpy, head, False);
+ } else {
+ free(head->next->next);
+ free(head->next);
+ free(head);
+ Assert (False, "traverse_points_list: unknown configuration");
+ }
+
+ free(head->next);
+ free(head);
+}
+
+
+const struct jwxyz_vtbl gl_vtbl = {
+ root,
+ visual,
+ display_sources_data,
+
+ window_background,
+ draw_arc,
+ fill_rects,
+ gc_gcv,
+ gc_depth,
+ jwxyz_draw_string,
+
+ jwxyz_gl_copy_area,
+
+ DrawPoints,
+ DrawSegments,
+ CreateGC,
+ FreeGC,
+ ClearWindow,
+ SetClipMask,
+ SetClipOrigin,
+ FillPolygon,
+ DrawLines,
+ PutImage,
+ GetSubImage
+};
+
+#endif /* JWXYZ_GL -- entire file */
diff --git a/jwxyz/jwxyz-image.c b/jwxyz/jwxyz-image.c
new file mode 100644
index 0000000..b1b3332
--- /dev/null
+++ b/jwxyz/jwxyz-image.c
@@ -0,0 +1,527 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do things to an XImage that bear some resemblance to the
+ things that Xlib might have done.
+
+ This handles things when jwxyz-gl.c can't.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef JWXYZ_IMAGE /* entire file */
+
+#include "jwxyzI.h"
+#include "jwxyz.h"
+#include "jwxyz-timers.h"
+#include "pow2.h"
+
+#include <wchar.h>
+
+
+union color_bytes { // Hello, again.
+ uint32_t pixel;
+ uint8_t bytes[4];
+};
+
+struct jwxyz_Display {
+ const struct jwxyz_vtbl *vtbl; // Must come first.
+
+ Window main_window;
+ Visual visual;
+ struct jwxyz_sources_data *timers_data;
+
+ unsigned long window_background;
+};
+
+struct jwxyz_GC {
+ XGCValues gcv;
+ unsigned int depth;
+};
+
+
+extern const struct jwxyz_vtbl image_vtbl;
+
+Display *
+jwxyz_image_make_display (Window w, const unsigned char *rgba_bytes)
+{
+ Display *d = (Display *) calloc (1, sizeof(*d));
+ d->vtbl = &image_vtbl;
+
+ Visual *v = &d->visual;
+ v->class = TrueColor;
+ Assert (rgba_bytes[3] == 3, "alpha not last");
+ unsigned long masks[4];
+ for (unsigned i = 0; i != 4; ++i) {
+ union color_bytes color;
+ color.pixel = 0;
+ color.bytes[rgba_bytes[i]] = 0xff;
+ masks[i] = color.pixel;
+ }
+ v->red_mask = masks[0];
+ v->green_mask = masks[1];
+ v->blue_mask = masks[2];
+ v->alpha_mask = masks[3];
+
+ d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
+ d->window_background = BlackPixel(d,0);
+ d->main_window = w;
+
+ return d;
+}
+
+void
+jwxyz_image_free_display (Display *dpy)
+{
+ jwxyz_sources_free (dpy->timers_data);
+
+ free (dpy);
+}
+
+
+static jwxyz_sources_data *
+display_sources_data (Display *dpy)
+{
+ return dpy->timers_data;
+}
+
+
+static Window
+root (Display *dpy)
+{
+ return dpy->main_window;
+}
+
+static Visual *
+visual (Display *dpy)
+{
+ return &dpy->visual;
+}
+
+
+static void
+next_point(short *v, XPoint p, int mode)
+{
+ switch (mode) {
+ case CoordModeOrigin:
+ v[0] = p.x;
+ v[1] = p.y;
+ break;
+ case CoordModePrevious:
+ v[0] += p.x;
+ v[1] += p.y;
+ break;
+ default:
+ Assert (False, "next_point: bad mode");
+ break;
+ }
+}
+
+#define SEEK_DRAWABLE(d, x, y) \
+ SEEK_XY (jwxyz_image_data(d), jwxyz_image_pitch(d), x, y)
+
+static int
+DrawPoints (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int count, int mode)
+{
+ Assert (gc->gcv.function == GXcopy, "XDrawPoints: bad GC function");
+
+ const XRectangle *frame = jwxyz_frame (d);
+ short v[2] = {0, 0};
+ for (unsigned i = 0; i < count; i++) {
+ next_point(v, points[i], mode);
+ if (v[0] >= 0 && v[0] < frame->width &&
+ v[1] >= 0 && v[1] < frame->height)
+ *SEEK_DRAWABLE(d, v[0], v[1]) = gc->gcv.foreground;
+ }
+
+ return 0;
+}
+
+
+static void
+copy_area (Display *dpy, Drawable src, Drawable dst, GC gc,
+ int src_x, int src_y, unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+ jwxyz_blit (jwxyz_image_data (src), jwxyz_image_pitch (src), src_x, src_y,
+ jwxyz_image_data (dst), jwxyz_image_pitch (dst), dst_x, dst_y,
+ width, height);
+}
+
+
+static void
+draw_line (Drawable d, unsigned long pixel,
+ short x0, short y0, short x1, short y1)
+{
+// TODO: Assert line_Width == 1, line_stipple == solid, etc.
+
+ const XRectangle *frame = jwxyz_frame (d);
+ if (x0 < 0 || x0 >= frame->width ||
+ x1 < 0 || x1 >= frame->width ||
+ y0 < 0 || y0 >= frame->height ||
+ y1 < 0 || y1 >= frame->height) {
+ Log ("draw_line: out of bounds");
+ return;
+ }
+
+ int dx = abs(x1 - x0), dy = abs(y1 - y0);
+
+ unsigned dmod0, dmod1;
+ int dpx0, dpx1;
+ if (dx > dy) {
+ dmod0 = dy;
+ dmod1 = dx;
+ dpx0 = x1 > x0 ? 1 : -1;
+ dpx1 = y1 > y0 ? frame->width : -frame->width;
+ } else {
+ dmod0 = dx;
+ dmod1 = dy;
+ dpx0 = y1 > y0 ? frame->width : -frame->width;
+ dpx1 = x1 > x0 ? 1 : -1;
+ }
+
+ unsigned n = dmod1;
+ unsigned mod = n;
+ ++n;
+
+ dmod0 <<= 1;
+ dmod1 <<= 1;
+
+ uint32_t *px = SEEK_DRAWABLE(d, x0, y0);
+
+ for(; n; --n) {
+ *px = pixel;
+
+ mod += dmod0;
+ if(mod > dmod1) {
+ mod -= dmod1;
+ px += dpx1;
+ }
+
+ px += dpx0;
+ }
+}
+
+static int
+DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
+ int mode)
+{
+ short v[2] = {0, 0}, v_prev[2] = {0, 0};
+ unsigned long pixel = gc->gcv.foreground;
+ for (unsigned i = 0; i != count; ++i) {
+ next_point(v, points[i], mode);
+ if (i)
+ draw_line (d, pixel, v_prev[0], v_prev[1], v[0], v[1]);
+ v_prev[0] = v[0];
+ v_prev[1] = v[1];
+ }
+ return 0;
+}
+
+
+static int
+DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
+{
+ unsigned long pixel = gc->gcv.foreground;
+ for (unsigned i = 0; i != count; ++i) {
+ XSegment *seg = &segments[i];
+ draw_line (d, pixel, seg->x1, seg->y1, seg->x2, seg->y2);
+ }
+ return 0;
+}
+
+
+static int
+ClearWindow (Display *dpy, Window win)
+{
+ Assert (win == dpy->main_window, "not a window");
+ const XRectangle *wr = jwxyz_frame (win);
+ return XClearArea (dpy, win, 0, 0, wr->width, wr->height, 0);
+}
+
+static unsigned long *
+window_background (Display *dpy)
+{
+ return &dpy->window_background;
+}
+
+static void
+fill_rects (Display *dpy, Drawable d, GC gc,
+ const XRectangle *rectangles, unsigned long nrectangles,
+ unsigned long pixel)
+{
+ Assert (!gc || gc->gcv.function == GXcopy, "XDrawPoints: bad GC function");
+
+ const XRectangle *frame = jwxyz_frame (d);
+ void *image_data = jwxyz_image_data (d);
+ ptrdiff_t image_pitch = jwxyz_image_pitch (d);
+
+ for (unsigned i = 0; i != nrectangles; ++i) {
+ const XRectangle *rect = &rectangles[i];
+ unsigned x0 = rect->x >= 0 ? rect->x : 0, y0 = rect->y >= 0 ? rect->y : 0;
+ int x1 = rect->x + rect->width, y1 = rect->y + rect->height;
+ if (y1 > frame->height)
+ y1 = frame->height;
+ if (x1 > frame->width)
+ x1 = frame->width;
+ unsigned x_size = x1 - x0, y_size = y1 - y0;
+ void *dst = SEEK_XY (image_data, image_pitch, x0, y0);
+ while (y_size) {
+# if __SIZEOF_WCHAR_T__ == 4
+ wmemset (dst, (wchar_t) pixel, x_size);
+# else
+ for(size_t i = 0; i != x_size; ++i)
+ ((uint32_t *)dst)[i] = pixel;
+# endif
+ --y_size;
+ dst = (char *) dst + image_pitch;
+ }
+ }
+}
+
+
+static int
+FillPolygon (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int npoints, int shape, int mode)
+{
+ Log ("XFillPolygon: not implemented");
+ return 0;
+}
+
+static int
+draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ Log ("jwxyz_draw_arc: not implemented");
+ return 0;
+}
+
+
+static XGCValues *
+gc_gcv (GC gc)
+{
+ return &gc->gcv;
+}
+
+
+static unsigned int
+gc_depth (GC gc)
+{
+ return gc->depth;
+}
+
+
+static GC
+CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
+{
+ struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
+ gc->depth = jwxyz_drawable_depth (d);
+
+ jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
+ XChangeGC (dpy, gc, mask, xgcv);
+ return gc;
+}
+
+
+static int
+FreeGC (Display *dpy, GC gc)
+{
+ if (gc->gcv.font)
+ XUnloadFont (dpy, gc->gcv.font);
+
+ free (gc);
+ return 0;
+}
+
+
+static int
+PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h)
+{
+ const XRectangle *wr = jwxyz_frame (d);
+
+ Assert (gc, "no GC");
+ Assert ((w < 65535), "improbably large width");
+ Assert ((h < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
+ Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
+
+ // Clip width and height to the bounds of the Drawable
+ //
+ if (dest_x + w > wr->width) {
+ if (dest_x > wr->width)
+ return 0;
+ w = wr->width - dest_x;
+ }
+ if (dest_y + h > wr->height) {
+ if (dest_y > wr->height)
+ return 0;
+ h = wr->height - dest_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ // Clip width and height to the bounds of the XImage
+ //
+ if (src_x + w > ximage->width) {
+ if (src_x > ximage->width)
+ return 0;
+ w = ximage->width - src_x;
+ }
+ if (src_y + h > ximage->height) {
+ if (src_y > ximage->height)
+ return 0;
+ h = ximage->height - src_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ /* Assert (d->win */
+
+ if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
+ return 0;
+
+ XGCValues *gcv = gc_gcv (gc);
+
+ Assert (gcv->function == GXcopy, "XPutImage: bad GC function");
+ Assert (!ximage->xoffset, "XPutImage: bad xoffset");
+
+ ptrdiff_t
+ src_pitch = ximage->bytes_per_line,
+ dst_pitch = jwxyz_image_pitch (d);
+
+ const void *src_ptr = SEEK_XY (ximage->data, src_pitch, src_x, src_y);
+ void *dst_ptr = SEEK_XY (jwxyz_image_data (d), dst_pitch, dest_x, dest_y);
+
+ if (gcv->alpha_allowed_p) {
+ Assert (ximage->depth == 32, "XPutImage: depth != 32");
+ Assert (ximage->format == ZPixmap, "XPutImage: bad format");
+ Assert (ximage->bits_per_pixel == 32, "XPutImage: bad bits_per_pixel");
+
+ const uint8_t *src_row = src_ptr;
+ uint8_t *dst_row = dst_ptr;
+
+ /* Slight loss of precision here: color values may end up being one less
+ than what they should be.
+ */
+ while (h) {
+ for (unsigned x = 0; x != w; ++x) {
+ // Pixmaps don't contain alpha. (Yay.)
+ const uint8_t *src = src_row + x * 4;
+ uint8_t *dst = dst_row + x * 4;
+
+ // ####: This is pretty SIMD friendly.
+ // Protip: Align dst (load + store), let src be unaligned (load only)
+ uint16_t alpha = src[3], alpha1 = 0xff - src[3];
+ dst[0] = (src[0] * alpha + dst[0] * alpha1) >> 8;
+ dst[1] = (src[1] * alpha + dst[1] * alpha1) >> 8;
+ dst[2] = (src[2] * alpha + dst[2] * alpha1) >> 8;
+ }
+
+ src_row += src_pitch;
+ dst_row += dst_pitch;
+ --h;
+ }
+ } else {
+ Assert (ximage->depth == 1 || ximage->depth == 32,
+ "XPutImage: depth != 1 && depth != 32");
+
+ if (ximage->depth == 32) {
+ Assert (ximage->format == ZPixmap, "XPutImage: bad format");
+ Assert (ximage->bits_per_pixel == 32, "XPutImage: bad bits_per_pixel");
+ jwxyz_blit (ximage->data, ximage->bytes_per_line, src_x, src_y,
+ jwxyz_image_data (d), jwxyz_image_pitch (d), dest_x, dest_y,
+ w, h);
+ } else {
+ Log ("XPutImage: depth == 1");
+ }
+ }
+
+ return 0;
+}
+
+static XImage *
+GetSubImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format,
+ XImage *dest_image, int dest_x, int dest_y)
+{
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((x < 65535 && x > -65535), "improbably large x");
+ Assert ((y < 65535 && y > -65535), "improbably large y");
+
+ Assert (dest_image->depth == 32 && jwxyz_drawable_depth (d) == 32,
+ "XGetSubImage: bad depth");
+ Assert (format == ZPixmap, "XGetSubImage: bad format");
+
+ jwxyz_blit (jwxyz_image_data (d), jwxyz_image_pitch (d), x, y,
+ dest_image->data, dest_image->bytes_per_line, dest_x, dest_y,
+ width, height);
+
+ return dest_image;
+}
+
+
+static int
+SetClipMask (Display *dpy, GC gc, Pixmap m)
+{
+ Log ("TODO: No clip masks yet"); // Slip/colorbars.c needs this.
+ return 0;
+}
+
+static int
+SetClipOrigin (Display *dpy, GC gc, int x, int y)
+{
+ gc->gcv.clip_x_origin = x;
+ gc->gcv.clip_y_origin = y;
+ return 0;
+}
+
+
+const struct jwxyz_vtbl image_vtbl = {
+ root,
+ visual,
+ display_sources_data,
+
+ window_background,
+ draw_arc,
+ fill_rects,
+ gc_gcv,
+ gc_depth,
+ jwxyz_draw_string,
+
+ copy_area,
+
+ DrawPoints,
+ DrawSegments,
+ CreateGC,
+ FreeGC,
+ ClearWindow,
+ SetClipMask,
+ SetClipOrigin,
+ FillPolygon,
+ DrawLines,
+ PutImage,
+ GetSubImage
+};
+
+#endif /* JWXYZ_IMAGE -- entire file */
diff --git a/jwxyz/jwxyz-timers.c b/jwxyz/jwxyz-timers.c
new file mode 100644
index 0000000..67326b0
--- /dev/null
+++ b/jwxyz/jwxyz-timers.c
@@ -0,0 +1,357 @@
+/* xscreensaver, Copyright (c) 2006-2017 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* This is the portable implementation of Xt timers and inputs, for libjwxyz.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef HAVE_JWXYZ /* whole file */
+
+
+#undef DEBUG_TIMERS
+#undef DEBUG_SOURCES
+
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include "jwxyz.h"
+#include "jwxyz-timers.h"
+
+#ifdef HAVE_ANDROID
+extern void Log(const char *format, ...);
+#endif
+
+#ifdef HAVE_COCOA
+# define Log(S, ...) fprintf(stderr, "xscreensaver: " S "\n", __VA_ARGS__)
+#endif
+
+#ifdef DEBUG_TIMERS
+# define LOGT(...) Log(__VA_ARGS__)
+#else
+# define LOGT(...)
+#endif
+
+#ifdef DEBUG_SOURCES
+# define LOGI(...) Log(__VA_ARGS__)
+#else
+# define LOGI(...)
+#endif
+
+#define ASSERT_RET(C,S) do { \
+ if (!(C)) { \
+ jwxyz_abort ("jwxyz-timers: %s",(S)); \
+ return; \
+ }} while(0)
+
+#define ASSERT_RET0(C,S) do { \
+ if (!(C)) { \
+ jwxyz_abort ("jwxyz-timers: %s",(S)); \
+ return 0; \
+ }} while(0)
+
+
+XtAppContext
+XtDisplayToApplicationContext (Display *dpy)
+{
+ return (XtAppContext) dpy;
+}
+
+#define app_to_display(APP) ((Display *) (APP))
+
+#define DISPLAY_SOURCES_DATA(APP) \
+ JWXYZ_VTBL(app_to_display (APP))->display_sources_data (app_to_display (APP))
+
+
+struct jwxyz_sources_data {
+ int fd_count;
+ XtInputId ids[FD_SETSIZE];
+ XtIntervalId all_timers;
+};
+
+struct jwxyz_XtIntervalId {
+ XtAppContext app;
+ int refcount;
+
+ double run_at;
+ XtTimerCallbackProc cb;
+ XtPointer closure;
+
+ XtIntervalId next;
+};
+
+struct jwxyz_XtInputId {
+ XtAppContext app;
+ int refcount;
+
+ XtInputCallbackProc cb;
+ XtPointer closure;
+ int fd;
+};
+
+
+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));
+}
+
+
+jwxyz_sources_data *
+jwxyz_sources_init (XtAppContext app)
+{
+ jwxyz_sources_data *td = (jwxyz_sources_data *) calloc (1, sizeof (*td));
+ return td;
+}
+
+XtIntervalId
+XtAppAddTimeOut (XtAppContext app, unsigned long msecs,
+ XtTimerCallbackProc cb, XtPointer closure)
+{
+ jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
+ XtIntervalId data = (XtIntervalId) calloc (1, sizeof(*data));
+ double now = double_time();
+ data->app = app;
+ data->cb = cb;
+ data->closure = closure;
+ data->refcount++;
+ data->run_at = now + (msecs / 1000.0);
+
+ data->next = td->all_timers;
+ td->all_timers = data;
+
+ LOGT("timer 0x%08lX: alloc %lu %.2f", (unsigned long) data, msecs,
+ data->run_at - now);
+
+ return data;
+}
+
+
+/* This is called both by the user to manually kill a timer,
+ and by the run loop after a timer has fired.
+ */
+void
+XtRemoveTimeOut (XtIntervalId data)
+{
+ jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (data->app);
+
+ LOGT("timer 0x%08lX: remove", (unsigned long) data);
+ ASSERT_RET (data->refcount > 0, "already freed");
+
+ data->refcount--;
+ LOGT("timer 0x%08lX: release %d", (unsigned long) data, data->refcount);
+ ASSERT_RET (data->refcount >= 0, "double free");
+
+ if (data->refcount == 0) {
+
+ /* Remove it from the list of live timers. */
+ XtIntervalId prev, timer;
+ int hit = 0;
+ for (timer = td->all_timers, prev = 0;
+ timer;
+ prev = timer, timer = timer->next) {
+ if (timer == data) {
+ ASSERT_RET (!hit, "circular timer list");
+ if (prev)
+ prev->next = timer->next;
+ else
+ td->all_timers = timer->next;
+ timer->next = 0;
+ hit = 1;
+ } else {
+ ASSERT_RET (timer->refcount > 0, "timer list corrupted");
+ }
+ }
+
+ free (data);
+ }
+}
+
+
+XtInputId
+XtAppAddInput (XtAppContext app, int fd, XtPointer flags,
+ XtInputCallbackProc cb, XtPointer closure)
+{
+ jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
+ XtInputId data = (XtInputId) calloc (1, sizeof(*data));
+ data->cb = cb;
+ data->fd = fd;
+ data->closure = closure;
+ data->app = app;
+ data->refcount++;
+
+ LOGI("source 0x%08lX %2d: alloc", (unsigned long) data, data->fd);
+
+ ASSERT_RET0 (fd > 0 && fd < FD_SETSIZE, "fd out of range");
+ ASSERT_RET0 (td->ids[fd] == 0, "sources corrupted");
+ td->ids[fd] = data;
+ td->fd_count++;
+
+ return data;
+}
+
+
+void
+XtRemoveInput (XtInputId id)
+{
+ jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (id->app);
+
+ LOGI("source 0x%08lX %2d: remove", (unsigned long) id, id->fd);
+ ASSERT_RET (id->refcount > 0, "sources corrupted");
+ ASSERT_RET (td->fd_count > 0, "sources corrupted");
+ ASSERT_RET (id->fd > 0 && id->fd < FD_SETSIZE, "fd out of range");
+ ASSERT_RET (td->ids[id->fd] == id, "sources corrupted");
+
+ td->ids[id->fd] = 0;
+ td->fd_count--;
+ id->refcount--;
+
+ LOGI("source 0x%08lX %2d: release %d", (unsigned long) id, id->fd,
+ id->refcount);
+ ASSERT_RET (id->refcount >= 0, "double free");
+ if (id->refcount == 0) {
+ memset (id, 0xA1, sizeof(*id));
+ id->fd = -666;
+ free (id);
+ }
+}
+
+
+static void
+jwxyz_timers_run (jwxyz_sources_data *td)
+{
+ /* Iterate the timer list, being careful because XtRemoveTimeOut removes
+ the current item from that list. */
+ if (td->all_timers) {
+ XtIntervalId timer, next;
+ double now = double_time();
+ int count = 0;
+
+ for (timer = td->all_timers, next = timer->next;
+ timer;
+ timer = next, next = (timer ? timer->next : 0)) {
+ if (timer->run_at <= now) {
+ LOGT("timer 0x%08lX: fire %.02f", (unsigned long) timer,
+ now - timer->run_at);
+ timer->cb (timer->closure, &timer);
+ XtRemoveTimeOut (timer);
+ count++;
+ ASSERT_RET (count < 10000, "way too many timers to run");
+ }
+ }
+ }
+}
+
+
+static void
+jwxyz_sources_run (jwxyz_sources_data *td)
+{
+ if (td->fd_count == 0) return;
+
+ struct timeval tv = { 0, };
+ fd_set fds;
+ int i;
+ int max = 0;
+
+ FD_ZERO (&fds);
+ for (i = 0; i < FD_SETSIZE; i++) {
+ if (td->ids[i]) {
+ FD_SET (i, &fds);
+ max = i;
+ }
+ }
+
+ ASSERT_RET (max > 0, "no fds");
+
+ if (0 < select (max+1, &fds, NULL, NULL, &tv)) {
+ for (i = 0; i < FD_SETSIZE; i++) {
+ if (FD_ISSET (i, &fds)) {
+ XtInputId id = td->ids[i];
+ ASSERT_RET (id && id->cb, "sources corrupted");
+ ASSERT_RET (id->fd == i, "sources corrupted");
+ id->cb (id->closure, &id->fd, &id);
+ }
+ }
+ }
+}
+
+
+static void
+jwxyz_XtRemoveInput_all (jwxyz_sources_data *td)
+{
+ int i;
+ for (i = 0; i < FD_SETSIZE; i++) {
+ XtInputId id = td->ids[i];
+ if (id) XtRemoveInput (id);
+ }
+}
+
+
+static void
+jwxyz_XtRemoveTimeOut_all (jwxyz_sources_data *td)
+{
+ XtIntervalId timer, next;
+ int count = 0;
+
+ /* Iterate the timer list, being careful because XtRemoveTimeOut removes
+ the current item from that list. */
+ if (td->all_timers) {
+ for (timer = td->all_timers, next = timer->next;
+ timer;
+ timer = next, next = (timer ? timer->next : 0)) {
+ XtRemoveTimeOut (timer);
+ count++;
+ ASSERT_RET (count < 10000, "way too many timers to free");
+ }
+ ASSERT_RET (!td->all_timers, "timer list didn't empty");
+ }
+}
+
+
+void
+jwxyz_sources_free (jwxyz_sources_data *td)
+{
+ jwxyz_XtRemoveInput_all (td);
+ jwxyz_XtRemoveTimeOut_all (td);
+ memset (td, 0xA1, sizeof(*td));
+ free (td);
+}
+
+
+XtInputMask
+XtAppPending (XtAppContext app)
+{
+ return XtIMAlternateInput; /* just always say yes */
+}
+
+void
+XtAppProcessEvent (XtAppContext app, XtInputMask mask)
+{
+ jwxyz_sources_data *td = DISPLAY_SOURCES_DATA (app);
+ if (mask & XtIMAlternateInput)
+ jwxyz_sources_run (td);
+ if (mask & XtIMTimer)
+ jwxyz_timers_run (td);
+}
+
+#endif /* HAVE_JWXYZ */
diff --git a/jwxyz/jwxyz-timers.h b/jwxyz/jwxyz-timers.h
new file mode 100644
index 0000000..5326d82
--- /dev/null
+++ b/jwxyz/jwxyz-timers.h
@@ -0,0 +1,25 @@
+/* xscreensaver, Copyright (c) 2006-2016 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* This is an implementation of Xt timers, for libjwxyz.
+ */
+
+#ifndef __JWXYZ_TIMERS_H__
+#define __JWXYZ_TIMERS_H__
+
+#include "jwxyz.h"
+
+typedef struct jwxyz_sources_data jwxyz_sources_data;
+
+extern jwxyz_sources_data *jwxyz_sources_init (XtAppContext);
+extern void jwxyz_sources_free (jwxyz_sources_data *);
+
+#endif /* __JWXYZ_TIMERS_H__ */
diff --git a/jwxyz/jwxyz.h b/jwxyz/jwxyz.h
new file mode 100644
index 0000000..c70bdbe
--- /dev/null
+++ b/jwxyz/jwxyz.h
@@ -0,0 +1,906 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do Cocoa-ish or OpenGL-ish things that bear some resemblance
+ to the things that Xlib might have done.
+ */
+
+#ifndef __JWXYZ_H__
+#define __JWXYZ_H__
+
+#include <stdlib.h> /* For abort(). */
+#include <stdarg.h>
+
+#if defined __FreeBSD__ || defined __MACH__ && defined __APPLE__
+# include <sys/cdefs.h>
+#endif
+
+#ifndef __dead2
+/* __dead2 is an undocumented FreeBSD-ism (and by extension, an OSX-ism),
+ normally #defined in <sys/cdefs.h>.
+ */
+# if defined __GNUC__ || defined __clang__
+# define __dead2 __attribute__((__noreturn__))
+# else
+# define __dead2
+# endif
+#endif
+
+extern void jwxyz_abort(const char *fmt, ...) __dead2;
+#define abort() jwxyz_abort("abort in %s:%d", __FUNCTION__, __LINE__)
+
+typedef int Bool;
+typedef int Status;
+typedef void * XPointer;
+typedef unsigned long Time;
+typedef unsigned int KeySym;
+typedef unsigned int KeyCode;
+typedef unsigned long Atom; /* Must be as large as a char *. */
+
+typedef struct jwxyz_Display Display;
+typedef struct jwxyz_Display Screen;
+typedef struct jwxyz_Visual Visual;
+typedef struct jwxyz_Drawable * Drawable;
+typedef struct jwxyz_Colormap * Colormap;
+typedef struct jwxyz_GC * GC;
+typedef struct jwxyz_XColor XColor;
+typedef struct jwxyz_XGCValues XGCValues;
+typedef struct jwxyz_XPoint XPoint;
+typedef struct jwxyz_XSegment XSegment;
+typedef struct jwxyz_XRectangle XRectangle;
+typedef struct jwxyz_XArc XArc;
+typedef struct jwxyz_XWindowAttributes XWindowAttributes;
+typedef struct jwxyz_XrmOptionDescRec XrmOptionDescRec;
+typedef struct jwxyz_XrmDatabase * XrmDatabase;
+typedef struct jwxyz_XImage XImage;
+typedef struct jwxyz_XFontProp XFontProp;
+typedef struct jwxyz_XFontStruct XFontStruct;
+typedef struct jwxyz_Font * Font;
+typedef struct jwxyz_XFontSet * XFontSet;
+typedef struct jwxyz_XCharStruct XCharStruct;
+typedef struct jwxyz_XComposeStatus XComposeStatus;
+typedef struct jwxyz_XPixmapFormatValues XPixmapFormatValues;
+typedef struct jwxyz_XChar2b XChar2b;
+
+typedef union jwxyz_XEvent XEvent;
+typedef struct jwxyz_XAnyEvent XAnyEvent;
+typedef struct jwxyz_XKeyEvent XKeyEvent;
+typedef struct jwxyz_XMotionEvent XMotionEvent;
+typedef struct jwxyz_XButtonEvent XButtonEvent;
+typedef XKeyEvent XKeyPressedEvent;
+typedef XKeyEvent XKeyReleasedEvent;
+typedef XMotionEvent XPointerMovedEvent;
+typedef XButtonEvent XButtonPressedEvent;
+typedef XButtonEvent XButtonReleasedEvent;
+
+
+/* Not technically Xlib... */
+typedef struct jwxyz_GLXContext * GLXContext;
+typedef struct jwxyz_XtAppContext * XtAppContext;
+typedef struct jwxyz_XtIntervalId * XtIntervalId;
+typedef struct jwxyz_XtInputId * XtInputId;
+typedef void * XtPointer;
+typedef unsigned long XtInputMask;
+typedef struct jwxyz_linked_point linked_point;
+
+#define XtInputReadMask (1L<<0)
+#define XtInputWriteMask (1L<<1)
+#define XtInputExceptMask (1L<<2)
+#define XtIMXEvent 1
+#define XtIMTimer 2
+#define XtIMAlternateInput 4
+#define XtIMSignal 8
+#define XtIMAll (XtIMXEvent | XtIMTimer | XtIMAlternateInput | XtIMSignal)
+
+#define True 1
+#define TRUE 1
+#define False 0
+#define FALSE 0
+#define None 0
+
+#define Window Drawable
+#define Pixmap Drawable
+
+#define XrmoptionNoArg 0
+#define XrmoptionSepArg 1
+
+#define CoordModeOrigin 0
+#define CoordModePrevious 1
+
+#define LineSolid 0
+#define LineOnOffDash 1
+#define LineDoubleDash 2
+
+#define CapNotLast 0
+#define CapButt 1
+#define CapRound 2
+#define CapProjecting 3
+
+#define JoinMiter 0
+#define JoinRound 1
+#define JoinBevel 2
+
+#define FillSolid 0
+#define FillTiled 1
+#define FillStippled 2
+#define FillOpaqueStippled 3
+
+#define EvenOddRule 0
+#define WindingRule 1
+
+#define Complex 0
+#define Nonconvex 1
+#define Convex 2
+
+#define XYBitmap 0
+#define XYPixmap 1
+#define ZPixmap 2
+
+#define AllocNone 0
+#define AllocAll 1
+
+#define StaticGray 0
+#define GrayScale 1
+#define StaticColor 2
+#define PseudoColor 3
+#define TrueColor 4
+#define DirectColor 5
+
+#define LSBFirst 0
+#define MSBFirst 1
+
+#define DoRed (1<<0)
+#define DoGreen (1<<1)
+#define DoBlue (1<<2)
+
+#define GCFunction (1L<<0)
+#define GCPlaneMask (1L<<1)
+#define GCForeground (1L<<2)
+#define GCBackground (1L<<3)
+#define GCLineWidth (1L<<4)
+#define GCLineStyle (1L<<5)
+#define GCCapStyle (1L<<6)
+#define GCJoinStyle (1L<<7)
+#define GCFillStyle (1L<<8)
+#define GCFillRule (1L<<9)
+#define GCTile (1L<<10)
+#define GCStipple (1L<<11)
+#define GCTileStipXOrigin (1L<<12)
+#define GCTileStipYOrigin (1L<<13)
+#define GCFont (1L<<14)
+#define GCSubwindowMode (1L<<15)
+#define GCGraphicsExposures (1L<<16)
+#define GCClipXOrigin (1L<<17)
+#define GCClipYOrigin (1L<<18)
+#define GCClipMask (1L<<19)
+#define GCDashOffset (1L<<20)
+#define GCDashList (1L<<21)
+#define GCArcMode (1L<<22)
+
+#define KeyPress 2
+#define KeyRelease 3
+#define ButtonPress 4
+#define ButtonRelease 5
+#define MotionNotify 6
+#define Expose 12
+#define GraphicsExpose 13
+#define NoExpose 14
+#define VisibilityNotify 15
+
+#define ClipByChildren 0
+#define IncludeInferiors 1
+
+#define KeyPressMask (1L<<0)
+#define KeyReleaseMask (1L<<1)
+#define ButtonPressMask (1L<<2)
+#define ButtonReleaseMask (1L<<3)
+#define PointerMotionMask (1L<<6)
+
+#define Button1 1
+#define Button2 2
+#define Button3 3
+#define Button4 4
+#define Button5 5
+
+#define ShiftMask (1<<0)
+#define LockMask (1<<1)
+#define ControlMask (1<<2)
+#define Mod1Mask (1<<3)
+#define Mod2Mask (1<<4)
+#define Mod3Mask (1<<5)
+#define Mod4Mask (1<<6)
+#define Mod5Mask (1<<7)
+#define Button1Mask (1<<8)
+#define Button2Mask (1<<9)
+#define Button3Mask (1<<10)
+#define Button4Mask (1<<11)
+#define Button5Mask (1<<12)
+
+#define XK_Shift_L 0xFFE1
+#define XK_Shift_R 0xFFE2
+#define XK_Control_L 0xFFE3
+#define XK_Control_R 0xFFE4
+#define XK_Caps_Lock 0xFFE5
+#define XK_Shift_Lock 0xFFE6
+#define XK_Meta_L 0xFFE7
+#define XK_Meta_R 0xFFE8
+#define XK_Alt_L 0xFFE9
+#define XK_Alt_R 0xFFEA
+#define XK_Super_L 0xFFEB
+#define XK_Super_R 0xFFEC
+#define XK_Hyper_L 0xFFED
+#define XK_Hyper_R 0xFFEE
+
+#define XK_Home 0xFF50
+#define XK_Left 0xFF51
+#define XK_Up 0xFF52
+#define XK_Right 0xFF53
+#define XK_Down 0xFF54
+#define XK_Prior 0xFF55
+#define XK_Page_Up 0xFF55
+#define XK_Next 0xFF56
+#define XK_Page_Down 0xFF56
+#define XK_End 0xFF57
+#define XK_Begin 0xFF58
+
+#define XK_F1 0xFFBE
+#define XK_F2 0xFFBF
+#define XK_F3 0xFFC0
+#define XK_F4 0xFFC1
+#define XK_F5 0xFFC2
+#define XK_F6 0xFFC3
+#define XK_F7 0xFFC4
+#define XK_F8 0xFFC5
+#define XK_F9 0xFFC6
+#define XK_F10 0xFFC7
+#define XK_F11 0xFFC8
+#define XK_F12 0xFFC9
+
+
+#define GXclear 0x0 /* 0 */
+#define GXand 0x1 /* src AND dst */
+// #define GXandReverse 0x2 /* src AND NOT dst */
+#define GXcopy 0x3 /* src */
+// #define GXandInverted 0x4 /* NOT src AND dst */
+// #define GXnoop 0x5 /* dst */
+#define GXxor 0x6 /* src XOR dst */
+#define GXor 0x7 /* src OR dst */
+// #define GXnor 0x8 /* NOT src AND NOT dst */
+// #define GXequiv 0x9 /* NOT src XOR dst */
+// #define GXinvert 0xa /* NOT dst */
+// #define GXorReverse 0xb /* src OR NOT dst */
+// #define GXcopyInverted 0xc /* NOT src */
+// #define GXorInverted 0xd /* NOT src OR dst */
+// #define GXnand 0xe /* NOT src OR NOT dst */
+#define GXset 0xf /* 1 */
+
+#define XA_FONT 18
+
+#define DefaultScreen(dpy) (0)
+#define BlackPixelOfScreen XBlackPixelOfScreen
+#define WhitePixelOfScreen XWhitePixelOfScreen
+#define BlackPixel(dpy,n) BlackPixelOfScreen(ScreenOfDisplay(dpy,n))
+#define WhitePixel(dpy,n) WhitePixelOfScreen(ScreenOfDisplay(dpy,n))
+#define CellsOfScreen XCellsOfScreen
+#define XFree(x) free(x)
+#define BitmapPad(dpy) (8)
+#define BitmapBitOrder(dpy) (MSBFirst)
+#define ImageByteOrder(dpy) (MSBFirst)
+#define DisplayOfScreen XDisplayOfScreen
+#define DefaultScreenOfDisplay XDefaultScreenOfDisplay
+#define ScreenOfDisplay(dpy,n) DefaultScreenOfDisplay(dpy)
+#define DefaultVisualOfScreen XDefaultVisualOfScreen
+#define DefaultColormapOfScreen(s) (0)
+#define RootWindow XRootWindow
+#define RootWindowOfScreen(s) RootWindow(DisplayOfScreen(s),0)
+#define DisplayWidth XDisplayWidth
+#define DisplayHeight XDisplayHeight
+#define XMaxRequestSize(dpy) (65535)
+#define XWidthOfScreen(s) (DisplayWidth(DisplayOfScreen(s),0))
+#define XHeightOfScreen(s) (DisplayHeight(DisplayOfScreen(s),0))
+#define XWidthMMOfScreen(s) (XDisplayWidthMM(DisplayOfScreen(s),0))
+#define XHeightMMOfScreen(s) (XDisplayHeightMM(DisplayOfScreen(s),0))
+#define XDefaultScreenOfDisplay(d) (d)
+#define XDisplayOfScreen(s) (s)
+#define XDisplayNumberOfScreen(s) 0
+#define XScreenNumberOfScreen(s) 0
+
+extern int XDisplayWidth (Display *, int);
+extern int XDisplayHeight (Display *, int);
+extern int XDisplayWidthMM (Display *, int);
+extern int XDisplayHeightMM (Display *, int);
+
+unsigned long XBlackPixelOfScreen(Screen *);
+unsigned long XWhitePixelOfScreen(Screen *);
+unsigned long XCellsOfScreen(Screen *);
+
+extern int XDrawPoint (Display *, Drawable, GC, int x, int y);
+
+extern int XChangeGC (Display *, GC, unsigned long mask, XGCValues *);
+
+extern int XClearArea (Display *, Window, int x, int y, int w, int h,Bool exp);
+extern int XSetWindowBackground (Display *, Window, unsigned long);
+extern Status XGetWindowAttributes (Display *, Window, XWindowAttributes *);
+extern Status XGetGeometry (Display *, Drawable, Window *root_ret,
+ int *x_ret, int *y_ret,
+ unsigned int *w_ret, unsigned int *h_ret,
+ unsigned int *bw_ret, unsigned int *depth_ret);
+extern Status XAllocColor (Display *, Colormap, XColor *);
+extern Status XAllocColorCells (Display *, Colormap, Bool contig,
+ unsigned long *pmret, unsigned int npl,
+ unsigned long *pxret, unsigned int npx);
+extern int XStoreColors (Display *, Colormap, XColor *, int n);
+extern int XStoreColor (Display *, Colormap, XColor *);
+extern Status XParseColor(Display *, Colormap, const char *spec, XColor *ret);
+extern Status XAllocNamedColor (Display *, Colormap, char *name,
+ XColor *screen_ret, XColor *exact_ret);
+extern int XQueryColor (Display *, Colormap, XColor *);
+extern int XQueryColors(Display *, Colormap colormap, XColor *, int ncolors);
+
+extern int XSetForeground (Display *, GC, unsigned long);
+extern int XSetBackground (Display *, GC, unsigned long);
+extern int XSetFunction (Display *, GC, int);
+extern int XSetSubwindowMode (Display *, GC, int);
+extern int XSetLineAttributes (Display *, GC, unsigned int line_width,
+ int line_style, int cap_style, int join_style);
+extern int jwxyz_XSetAlphaAllowed (Display *, GC, Bool);
+extern int jwxyz_XSetAntiAliasing (Display *, GC, Bool);
+
+extern int XFlush (Display *);
+extern int XSync (Display *, Bool);
+extern int XFreeColors (Display *, Colormap, unsigned long *px, int n,
+ unsigned long planes);
+extern int XCopyArea (Display *, Drawable src, Drawable dest, GC,
+ int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dest_x, int dest_y);
+extern int XCopyPlane (Display *, Drawable, Drawable, GC,
+ int src_x, int src_y,
+ unsigned width, int height,
+ int dest_x, int dest_y,
+ unsigned long plane);
+
+extern int XDrawLine (Display *, Drawable, GC, int x1, int y1, int x2, int y2);
+extern int XDrawArc (Display *, Drawable, GC, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2);
+extern int XFillArc (Display *, Drawable, GC, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2);
+extern int XDrawArcs (Display *, Drawable, GC, XArc *arcs, int narcs);
+extern int XFillArcs (Display *, Drawable, GC, XArc *arcs, int narcs);
+extern int XDrawRectangle (Display *, Drawable, GC, int x, int y,
+ unsigned int width, unsigned int height);
+extern int XFillRectangle (Display *, Drawable, GC, int x, int y,
+ unsigned int width, unsigned int height);
+extern int XFillRectangles (Display *, Drawable, GC, XRectangle *, int n);
+
+extern int XDrawString (Display *, Drawable, GC, int x, int y, const char *,
+ int len);
+extern int XDrawImageString (Display *, Drawable, GC, int x, int y,
+ const char *, int len);
+extern int XDrawString16 (Display *, Drawable, GC, int x, int y,
+ const XChar2b *, int len);
+
+extern Bool XQueryPointer (Display *, Window, Window *root_ret,
+ Window *child_ret,
+ int *root_x_ret, int *root_y_ret,
+ int *win_x_ret, int *win_y_ret,
+ unsigned int *mask_ret);
+extern int XLookupString (XKeyEvent *, char *ret, int size, KeySym *ks_ret,
+ XComposeStatus *);
+extern KeySym XKeycodeToKeysym (Display *, KeyCode, int index);
+
+extern Status XInitImage (XImage *);
+extern XImage *XCreateImage (Display *, Visual *, unsigned int depth,
+ int format, int offset, char *data,
+ unsigned int width, unsigned int height,
+ int bitmap_pad, int bytes_per_line);
+extern XImage *XSubImage (XImage *, int x, int y,
+ unsigned int w, unsigned int h);
+
+extern unsigned long XGetPixel (XImage *, int x, int y);
+extern int XPutPixel (XImage *, int x, int y, unsigned long);
+extern int XDestroyImage (XImage *);
+extern XImage *XGetImage (Display *, Drawable, int x, int y,
+ unsigned int w, unsigned int h,
+ unsigned long pm, int fmt);
+extern Pixmap XCreatePixmapFromBitmapData (Display *, Drawable,
+ const char *data,
+ unsigned int w, unsigned int h,
+ unsigned long fg,
+ unsigned long bg,
+ unsigned int depth);
+extern XPixmapFormatValues *XListPixmapFormats (Display *, int *count_ret);
+
+extern void jwxyz_draw_NSImage_or_CGImage (Display *, Drawable,
+ Bool nsimg_p, void *NSImage_arg,
+ XRectangle *geom_ret,
+ int exif_rotation);
+extern XImage *jwxyz_png_to_ximage (Display *, Visual *,
+ const unsigned char *, unsigned long size);
+
+extern int XSetGraphicsExposures (Display *, GC, Bool);
+extern Bool XTranslateCoordinates (Display *, Window src_w, Window dest_w,
+ int src_x, int src_y,
+ int *dest_x_ret, int *dest_y_ret,
+ Window *child_ret);
+
+extern Font XLoadFont (Display *, const char *);
+extern XFontStruct * XQueryFont (Display *, Font);
+extern XFontStruct * XLoadQueryFont (Display *, const char *);
+extern int XFreeFontInfo (char **names, XFontStruct *info, int n);
+extern int XFreeFont (Display *, XFontStruct *);
+extern int XUnloadFont (Display *, Font);
+extern int XTextExtents (XFontStruct *, const char *, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *overall_ret);
+extern char * jwxyz_unicode_character_name (Display *, Font, unsigned long uc);
+extern int XTextExtents16 (XFontStruct *, const XChar2b *, int length,
+ int *dir_ret, int *ascent_ret, int *descent_ret,
+ XCharStruct *overall_ret);
+extern int XTextWidth (XFontStruct *, const char *, int length);
+extern int XSetFont (Display *, GC, Font);
+
+extern XFontSet XCreateFontSet (Display *, char *name,
+ char ***missing_charset_list_return,
+ int *missing_charset_count_return,
+ char **def_string_return);
+extern void XFreeFontSet (Display *, XFontSet);
+extern void XFreeStringList (char **);
+extern int Xutf8TextExtents (XFontSet, const char *, int num_bytes,
+ XRectangle *overall_ink_return,
+ XRectangle *overall_logical_return);
+extern void Xutf8DrawString (Display *, Drawable, XFontSet, GC,
+ int x, int y, const char *, int num_bytes);
+
+extern Pixmap XCreatePixmap (Display *, Drawable,
+ unsigned int width, unsigned int height,
+ unsigned int depth);
+extern int XFreePixmap (Display *, Pixmap);
+
+extern char *XGetAtomName (Display *, Atom);
+
+extern void set_points_list(XPoint *points, int npoints, linked_point *root);
+extern void traverse_points_list(Display *dpy, linked_point * root);
+extern void draw_three_vertices(Display *dpy, linked_point * a,
+ Bool triangle);
+extern double compute_edge_length(linked_point * a, linked_point * b);
+extern double get_angle(double a, double b, double c);
+extern Bool is_same_slope(linked_point * a);
+extern Bool is_an_ear(linked_point * a);
+extern Bool is_three_point_loop(linked_point * head);
+
+extern int draw_arc_gl(Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p);
+
+// Log()/Logv(), for debugging JWXYZ. Screenhacks should still use
+// fprintf(stderr, ...).
+extern void Log(const char *format, ...)
+#if defined __GNUC__ || defined __clang__
+ __attribute__((format(printf, 1, 2)))
+#endif
+ ;
+
+extern void jwxyz_logv(Bool error, const char *fmt, va_list args);
+#define Logv(format, args) (jwxyz_logv(False, format, args))
+
+// Xt timers and fds
+extern XtAppContext XtDisplayToApplicationContext (Display *);
+typedef void (*XtTimerCallbackProc) (XtPointer closure, XtIntervalId *);
+typedef void (*XtInputCallbackProc) (XtPointer closure, int *fd, XtInputId *);
+extern XtIntervalId XtAppAddTimeOut (XtAppContext, unsigned long usecs,
+ XtTimerCallbackProc, XtPointer closure);
+extern void XtRemoveTimeOut (XtIntervalId);
+extern XtInputId XtAppAddInput (XtAppContext, int fd, XtPointer flags,
+ XtInputCallbackProc, XtPointer closure);
+extern void XtRemoveInput (XtInputId);
+extern XtInputMask XtAppPending (XtAppContext);
+extern void XtAppProcessEvent (XtAppContext, XtInputMask);
+
+// Some GLX stuff that also doesn't technically belong here...
+// from XScreenSaverGLView.m
+extern void glXSwapBuffers (Display *, Window);
+extern void glXMakeCurrent (Display *, Window, GLXContext);
+
+// also declared in utils/visual.h
+extern int has_writable_cells (Screen *, Visual *);
+extern int visual_depth (Screen *, Visual *);
+extern int visual_pixmap_depth (Screen *, Visual *);
+extern int visual_cells (Screen *, Visual *);
+extern int visual_class (Screen *, Visual *);
+extern void visual_rgb_masks (Screen *screen, Visual *visual,
+ unsigned long *red_mask,
+ unsigned long *green_mask,
+ unsigned long *blue_mask);
+extern int screen_number (Screen *);
+
+// also declared in utils/grabclient.h
+extern Bool use_subwindow_mode_p (Screen *, Window);
+
+// also declared in xlockmoreI.h
+extern void clear_gl_error (void);
+extern void check_gl_error (const char *type);
+
+// A Visual is supposed to be an opaque type, even though Xlib.h defines it.
+// Only utils/xft.c uses this, out of necessity.
+struct jwxyz_Visual {
+ int class; /* class of screen (monochrome, etc.) */
+ unsigned long red_mask, green_mask, blue_mask; /* same as Xlib.h */
+ unsigned long alpha_mask; /* new */
+};
+
+struct jwxyz_XGCValues {
+ int function; /* logical operation */
+#if 0
+ unsigned long plane_mask;/* plane mask */
+#endif
+ unsigned long foreground;/* foreground pixel */
+ unsigned long background;/* background pixel */
+ int line_width; /* line width */
+#if 0
+ int line_style; /* LineSolid, LineOnOffDash, LineDoubleDash */
+#endif
+ int cap_style; /* CapNotLast, CapButt, CapRound, CapProjecting */
+ int join_style; /* JoinMiter, JoinRound, JoinBevel */
+#if 0
+ int fill_style; /* FillSolid, FillTiled,
+ FillStippled, FillOpaeueStippled */
+#endif
+ int fill_rule; /* EvenOddRule, WindingRule */
+#if 0
+ int arc_mode; /* ArcChord, ArcPieSlice */
+ Pixmap tile; /* tile pixmap for tiling operations */
+ Pixmap stipple; /* stipple 1 plane pixmap for stipping */
+ int ts_x_origin; /* offset for tile or stipple operations */
+ int ts_y_origin;
+#endif
+ Font font; /* default text font for text operations */
+ int subwindow_mode; /* ClipByChildren, IncludeInferiors */
+#if 0
+ Bool graphics_exposures;/* boolean, should exposures be generated */
+#endif
+ int clip_x_origin; /* origin for clipping */
+ int clip_y_origin;
+ Pixmap clip_mask; /* bitmap clipping; other calls for rects */
+#if 0
+ int dash_offset; /* patterned/dashed line information */
+ char dashes;
+#endif
+
+ Bool alpha_allowed_p; /* jwxyz extension: whether pixel values may have
+ a non-opaque alpha component. */
+ Bool antialias_p; /* jwxyz extension: whether Quartz should draw
+ with antialiasing. */
+};
+
+struct jwxyz_XWindowAttributes {
+ int x, y; /* location of window */
+ int width, height; /* width and height of window */
+ int border_width; /* border width of window */
+ int depth; /* depth of window */
+ Visual *visual; /* the associated visual structure */
+#if 0
+ Window root; /* root of screen containing window */
+ int class; /* InputOutput, InputOnly*/
+ int bit_gravity; /* one of bit gravity values */
+ int win_gravity; /* one of the window gravity values */
+ int backing_store; /* NotUseful, WhenMapped, Always */
+ unsigned long backing_planes;/* planes to be preserved if possible */
+ unsigned long backing_pixel;/* value to be used when restoring planes */
+ Bool save_under; /* boolean, should bits under be saved? */
+#endif
+ Colormap colormap; /* color map to be associated with window */
+#if 0
+ Bool map_installed; /* boolean, is color map currently installed*/
+ int map_state; /* IsUnmapped, IsUnviewable, IsViewable */
+ long all_event_masks; /* set of events all people have interest in*/
+ long your_event_mask; /* my event mask */
+ long do_not_propagate_mask; /* set of events that should not propagate */
+ Bool override_redirect; /* boolean value for override-redirect */
+#endif
+ Screen *screen; /* back pointer to correct screen */
+};
+
+struct jwxyz_XColor {
+ unsigned long pixel;
+ unsigned short red, green, blue;
+ char flags; /* do_red, do_green, do_blue */
+ char pad;
+};
+
+struct jwxyz_XPoint {
+ short x, y;
+};
+
+struct jwxyz_XSegment {
+ short x1, y1, x2, y2;
+};
+
+struct jwxyz_XRectangle {
+ short x, y;
+ unsigned short width, height;
+};
+
+struct jwxyz_XArc {
+ short x, y;
+ unsigned short width, height;
+ short angle1, angle2;
+};
+
+
+struct jwxyz_XrmOptionDescRec {
+ char *option;
+ char *specifier;
+ int argKind;
+ void *value;
+};
+
+struct jwxyz_XAnyEvent {
+ int type;
+#if 0
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+#endif
+};
+
+struct jwxyz_XKeyEvent {
+ int type;
+#if 0
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ Window root;
+ Window subwindow;
+ Time time;
+ int x, y;
+ int x_root, y_root;
+#endif
+ unsigned int state;
+ unsigned int keycode;
+#if 0
+ Bool same_screen;
+#endif
+};
+
+struct jwxyz_XButtonEvent {
+ int type;
+#if 0
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ Window root;
+ Window subwindow;
+ Time time;
+#endif
+ int x, y;
+#if 0
+ int x_root, y_root;
+#endif
+ unsigned int state;
+ unsigned int button;
+#if 0
+ Bool same_screen;
+#endif
+};
+
+struct jwxyz_XMotionEvent {
+ int type;
+#if 0
+ unsigned long serial;
+ Bool send_event;
+ Display *display;
+ Window window;
+ Window root;
+ Window subwindow;
+ Time time;
+#endif
+ int x, y;
+#if 0
+ int x_root, y_root;
+#endif
+ unsigned int state;
+#if 0
+ char is_hint;
+ Bool same_screen;
+#endif
+};
+
+union jwxyz_XEvent {
+ int type;
+ XAnyEvent xany;
+ XKeyEvent xkey;
+ XButtonEvent xbutton;
+ XMotionEvent xmotion;
+};
+
+struct jwxyz_XImage {
+ int width, height; /* size of image */
+ int xoffset; /* number of pixels offset in X direction */
+ int format; /* XYBitmap, XYPixmap, ZPixmap */
+ char *data; /* pointer to image data */
+ int byte_order; /* data byte order, LSBFirst, MSBFirst */
+ int bitmap_unit; /* quant. of scanline 8, 16, 32 */
+ int bitmap_bit_order; /* LSBFirst, MSBFirst */
+ int bitmap_pad; /* 8, 16, 32 either XY or ZPixmap */
+ int depth; /* depth of image */
+ int bytes_per_line; /* accelarator to next line */
+ int bits_per_pixel; /* bits per pixel (ZPixmap) */
+ unsigned long red_mask; /* bits in z arrangment */
+ unsigned long green_mask;
+ unsigned long blue_mask;
+// XPointer obdata; /* hook for the object routines to hang on */
+ struct funcs { /* image manipulation routines */
+#if 0
+ XImage *(*create_image)(
+ Display* /* display */,
+ Visual* /* visual */,
+ unsigned int /* depth */,
+ int /* format */,
+ int /* offset */,
+ char* /* data */,
+ unsigned int /* width */,
+ unsigned int /* height */,
+ int /* bitmap_pad */,
+ int /* bytes_per_line */);
+ int (*destroy_image) (XImage *);
+#endif
+ unsigned long (*get_pixel) (XImage *, int, int);
+ int (*put_pixel) (XImage *, int, int, unsigned long);
+#if 0
+ XImage *(*sub_image) (XImage *, int, int, unsigned int, unsigned int);
+ int (*add_pixel) (XImage *, long);
+#endif
+ } f;
+};
+
+struct jwxyz_XCharStruct {
+ short lbearing; /* origin to left edge of ink */
+ short rbearing; /* origin to right edge of ink */
+ short width; /* advance to next char's origin */
+ short ascent; /* baseline to top edge of ink */
+ short descent; /* baseline to bottom edge of ink */
+#if 0
+ unsigned short attributes; /* per char flags (not predefined) */
+#endif
+};
+
+struct jwxyz_XFontProp {
+ Atom name;
+ unsigned long card32; /* Careful: This holds (32- or 64-bit) pointers. */
+};
+
+struct jwxyz_XFontStruct {
+#if 0
+ XExtData *ext_data; /* hook for extension to hang data */
+#endif
+ Font fid; /* Font id for this font */
+#if 0
+ unsigned direction; /* hint about direction the font is painted */
+#endif
+ unsigned min_char_or_byte2; /* first character */
+ unsigned max_char_or_byte2; /* last character */
+#if 0
+ unsigned min_byte1; /* first row that exists */
+ unsigned max_byte1; /* last row that exists */
+ Bool all_chars_exist; /* flag if all characters have non-zero size*/
+#endif
+ unsigned default_char; /* char to print for undefined character */
+ int n_properties; /* how many properties there are */
+ XFontProp *properties; /* pointer to array of additional properties*/
+ XCharStruct min_bounds; /* minimum bounds over all existing char*/
+ XCharStruct max_bounds; /* maximum bounds over all existing char*/
+ XCharStruct *per_char; /* first_char to last_char information */
+ int ascent; /* log. extent above baseline for spacing */
+ int descent; /* log. descent below baseline for spacing */
+};
+
+struct jwxyz_XComposeStatus {
+ char dummy;
+};
+
+struct jwxyz_XPixmapFormatValues {
+ int depth;
+ int bits_per_pixel;
+ int scanline_pad;
+};
+
+struct jwxyz_XChar2b {
+ unsigned char byte1;
+ unsigned char byte2;
+};
+
+
+struct jwxyz_vtbl {
+ Window (*root) (Display *);
+ Visual *(*visual) (Display *);
+ struct jwxyz_sources_data *(*display_sources_data) (Display *);
+
+ unsigned long *(*window_background) (Display *);
+ int (*draw_arc) (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p);
+ void (*fill_rects) (Display *dpy, Drawable d, GC gc,
+ const XRectangle *rectangles,
+ unsigned long nrects, unsigned long pixel);
+ XGCValues *(*gc_gcv) (GC gc);
+ unsigned int (*gc_depth) (GC gc);
+ int (*draw_string) (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, size_t len, Bool utf8);
+
+ void (*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);
+
+ int (*DrawPoints) (Display *, Drawable, GC, XPoint *, int n, int mode);
+ int (*DrawSegments) (Display *, Drawable, GC, XSegment *, int n);
+ GC (*CreateGC) (Display *, Drawable, unsigned long mask, XGCValues *);
+ int (*FreeGC) (Display *, GC);
+ int (*ClearWindow) (Display *, Window);
+ int (*SetClipMask) (Display *, GC, Pixmap);
+ int (*SetClipOrigin) (Display *, GC, int x, int y);
+ int (*FillPolygon) (Display *, Drawable, GC,
+ XPoint * points, int npoints, int shape, int mode);
+ int (*DrawLines) (Display *, Drawable, GC, XPoint *, int n, int mode);
+
+ int (*PutImage) (Display *, Drawable, GC, XImage *,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h);
+ XImage *(*GetSubImage) (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format,
+ XImage *dest_image, int dest_x, int dest_y);
+};
+
+#define JWXYZ_VTBL(dpy) (*(struct jwxyz_vtbl **)(dpy))
+
+#define XRootWindow(dpy, screen) \
+ ((dpy) ? JWXYZ_VTBL(dpy)->root(dpy) : 0)
+#define XDefaultVisualOfScreen(screen) \
+ ((screen) ? JWXYZ_VTBL(screen)->visual(screen) : 0)
+
+#define XDrawPoints(dpy, d, gc, points, n, mode) \
+ (JWXYZ_VTBL(dpy)->DrawPoints (dpy, d, gc, points, n, mode))
+#define XDrawSegments(dpy, d, gc, segments, n) \
+ (JWXYZ_VTBL(dpy)->DrawSegments (dpy, d, gc, segments, n))
+#define XCreateGC(dpy, d, mask, gcv) \
+ (JWXYZ_VTBL(dpy)->CreateGC (dpy, d, mask, gcv))
+#define XFreeGC(dpy, gc) \
+ (JWXYZ_VTBL(dpy)->FreeGC (dpy, gc))
+#define XClearWindow(dpy, win) \
+ (JWXYZ_VTBL(dpy)->ClearWindow(dpy, win))
+#define XSetClipMask(dpy, gc, m) \
+ (JWXYZ_VTBL(dpy)->SetClipMask (dpy, gc, m))
+#define XSetClipOrigin(dpy, gc, x, y) \
+ (JWXYZ_VTBL(dpy)->SetClipOrigin (dpy, gc, x, y))
+#define XFillPolygon(dpy, d, gc, points, npoints, shape, mode) \
+ (JWXYZ_VTBL(dpy)->FillPolygon (dpy, d, gc, points, npoints, shape, mode))
+#define XDrawLines(dpy, d, gc, points, n, mode) \
+ (JWXYZ_VTBL(dpy)->DrawLines (dpy, d, gc, points, n, mode))
+#define XPutImage(dpy, d, gc, image, src_x, src_y, dest_x, dest_y, w, h) \
+ (JWXYZ_VTBL(dpy)->PutImage (dpy, d, gc, image, src_x, src_y, \
+ dest_x, dest_y, w, h))
+#define XGetSubImage(dpy, d, x, y, width, height, plane_mask, \
+ format, dest_image, dest_x, dest_y) \
+ (JWXYZ_VTBL(dpy)->GetSubImage (dpy, d, x, y, width, height, plane_mask, \
+ format, dest_image, dest_x, dest_y))
+
+
+#endif /* __JWXYZ_H__ */
diff --git a/jwxyz/jwxyz.m b/jwxyz/jwxyz.m
new file mode 100644
index 0000000..4256f6d
--- /dev/null
+++ b/jwxyz/jwxyz.m
@@ -0,0 +1,1812 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do Cocoa-ish things that bear some resemblance to the
+ things that Xlib might have done.
+
+ This is the original version of jwxyz for MacOS and iOS.
+ The version used by Android is in jwxyz-gl.c and jwxyz-common.c.
+ Both versions depend on jwxyz-cocoa.m.
+ */
+
+#ifdef JWXYZ_QUARTZ // entire file
+
+#import <stdlib.h>
+#import <stdint.h>
+#import <wchar.h>
+
+#ifdef USE_IPHONE
+# import <UIKit/UIKit.h>
+# import <UIKit/UIScreen.h>
+# import <QuartzCore/QuartzCore.h>
+# define NSView UIView
+# define NSRect CGRect
+# define NSPoint CGPoint
+# define NSSize CGSize
+# define NSColor UIColor
+# define NSImage UIImage
+# define NSEvent UIEvent
+# define NSFont UIFont
+# define NSGlyph CGGlyph
+# define NSWindow UIWindow
+# define NSMakeSize CGSizeMake
+# define NSBezierPath UIBezierPath
+# define colorWithDeviceRed colorWithRed
+#else
+# import <Cocoa/Cocoa.h>
+#endif
+
+#import <CoreText/CTFont.h>
+#import <CoreText/CTLine.h>
+
+#import "jwxyzI.h"
+#import "jwxyz-cocoa.h"
+#import "jwxyz-timers.h"
+#import "yarandom.h"
+#import "utf8wc.h"
+#import "xft.h"
+
+
+struct jwxyz_Display {
+ const struct jwxyz_vtbl *vtbl; // Must come first.
+
+ Window main_window;
+ CGBitmapInfo bitmap_info;
+ Visual visual;
+ struct jwxyz_sources_data *timers_data;
+
+# ifndef USE_IPHONE
+ CGDirectDisplayID cgdpy; /* ...of the one and only Window, main_window.
+ This can change if the window is dragged to
+ a different screen. */
+# endif
+
+ CGColorSpaceRef colorspace; /* Color space of this screen. We tag all of
+ our images with this to avoid translation
+ when rendering. */
+
+ unsigned long window_background;
+};
+
+struct jwxyz_GC {
+ XGCValues gcv;
+ unsigned int depth;
+ CGImageRef clip_mask; // CGImage copy of the Pixmap in gcv.clip_mask
+};
+
+
+// 8/16/24/32bpp -> 32bpp image conversion.
+// Any of RGBA, BGRA, ABGR, or ARGB can be represented by a rotate of 0/8/16/24
+// bits and an optional byte order swap.
+
+// This type encodes such a conversion.
+typedef unsigned convert_mode_t;
+
+// It's rotate, then swap.
+// A rotation here shifts bytes forward in memory. On x86/ARM, that's a left
+// rotate, and on PowerPC, a rightward rotation.
+static const convert_mode_t CONVERT_MODE_ROTATE_MASK = 0x3;
+static const convert_mode_t CONVERT_MODE_SWAP = 0x4;
+
+
+#if defined __LITTLE_ENDIAN__
+# define PAD(r, g, b, a) ((r) | ((g) << 8) | ((b) << 16) | ((a) << 24))
+#elif defined __BIG_ENDIAN__
+# define PAD(r, g, b, a) (((r) << 24) | ((g) << 16) | ((b) << 8) | (a))
+#else
+# error "Can't determine system endianness."
+#endif
+
+
+// Converts an array of pixels ('src') from one format to another, placing the
+// result in 'dest', according to the pixel conversion mode 'mode'.
+static void
+convert_row (uint32_t *dest, const void *src, size_t count,
+ convert_mode_t mode, size_t src_bpp)
+{
+ Assert (src_bpp == 8 || src_bpp == 24 || src_bpp == 16 || src_bpp == 32,
+ "weird bpp");
+
+ // This works OK iff src == dest or src and dest do not overlap.
+
+ if (!mode && src_bpp == 32) {
+ if (src != dest)
+ memcpy (dest, src, count * 4);
+ return;
+ }
+
+ // This is correct, but not fast.
+ convert_mode_t rot = (mode & CONVERT_MODE_ROTATE_MASK) * 8;
+ convert_mode_t flip = mode & CONVERT_MODE_SWAP;
+
+ src_bpp /= 8;
+
+ uint32_t *dest_end = dest + count;
+ while (dest != dest_end) {
+ uint32_t x;
+
+ const uint8_t *src8 = (const uint8_t *)src;
+ switch (src_bpp) {
+ case 4:
+ x = *(const uint32_t *)src;
+ break;
+ case 3:
+ x = PAD(src8[0], src8[1], src8[2], 0xff);
+ break;
+ case 2:
+ x = PAD(src8[0], src8[0], src8[0], src8[1]);
+ break;
+ case 1:
+ x = PAD(src8[0], src8[0], src8[0], 0xff);
+ break;
+ }
+
+ src = (const uint8_t *)src + src_bpp;
+
+ /* The naive (i.e. ubiquitous) portable implementation of bitwise rotation,
+ for 32-bit integers, is:
+
+ (x << rot) | (x >> (32 - rot))
+
+ This works nearly everywhere. Compilers on x86 wil generally recognize
+ the idiom and convert it to a ROL instruction. But there's a problem
+ here: according to the C specification, bit shifts greater than or equal
+ to the length of the integer are undefined. And if rot = 0:
+ 1. (x << 0) | (x >> (32 - 0))
+ 2. (x << 0) | (x >> 32)
+ 3. (x << 0) | (Undefined!)
+
+ Still, when the compiler converts this to a ROL on x86, everything works
+ as intended. But, there are two additional problems when Clang does
+ compile-time constant expression evaluation with the (x >> 32)
+ expression:
+ 1. Instead of evaluating it to something reasonable (either 0, like a
+ human would intuitively expect, or x, like x86 would with SHR), Clang
+ seems to pull a value out of nowhere, like -1, or some other random
+ number.
+ 2. Clang's warning for this, -Wshift-count-overflow, only works when the
+ shift count is a literal constant, as opposed to an arbitrary
+ expression that is optimized down to a constant.
+ Put together, this means that the assertions in
+ jwxyz_quartz_make_display with convert_px break with the above naive
+ rotation, but only for a release build.
+
+ http://blog.regehr.org/archives/1063
+ http://llvm.org/bugs/show_bug.cgi?id=17332
+ As described in those links, there is a solution here: Masking the
+ undefined shift with '& 31' as below makes the experesion well-defined
+ again. And LLVM is set to pick up on this safe version of the idiom and
+ use a rotation instruction on architectures (like x86) that support it,
+ just like it does with the unsafe version.
+
+ Too bad LLVM doesn't want to pick up on that particular optimization
+ here. Oh well. At least this code usually isn't critical w.r.t.
+ performance.
+ */
+
+# if defined __LITTLE_ENDIAN__
+ x = (x << rot) | (x >> ((32 - rot) & 31));
+# elif defined __BIG_ENDIAN__
+ x = (x >> rot) | (x << ((32 - rot) & 31));
+# endif
+
+ if (flip)
+ x = __builtin_bswap32(x); // LLVM/GCC built-in function.
+
+ *dest = x;
+ ++dest;
+ }
+}
+
+
+// Converts a single pixel.
+static uint32_t
+convert_px (uint32_t px, convert_mode_t mode)
+{
+ convert_row (&px, &px, 1, mode, 32);
+ return px;
+}
+
+
+// This returns the inverse conversion mode, such that:
+// pixel
+// == convert_px(convert_px(pixel, mode), convert_mode_invert(mode))
+// == convert_px(convert_px(pixel, convert_mode_invert(mode)), mode)
+static convert_mode_t
+convert_mode_invert (convert_mode_t mode)
+{
+ // swap(0); rot(n) == rot(n); swap(0)
+ // swap(1); rot(n) == rot(-n); swap(1)
+ return mode & CONVERT_MODE_SWAP ? mode : CONVERT_MODE_ROTATE_MASK & -mode;
+}
+
+
+// This combines two conversions into one, such that:
+// convert_px(convert_px(pixel, mode0), mode1)
+// == convert_px(pixel, convert_mode_merge(mode0, mode1))
+static convert_mode_t
+convert_mode_merge (convert_mode_t m0, convert_mode_t m1)
+{
+ // rot(r0); swap(s0); rot(r1); swap(s1)
+ // rot(r0); rot(s0 ? -r1 : r1); swap(s0); swap(s1)
+ // rot(r0 + (s0 ? -r1 : r1)); swap(s0 + s1)
+ return
+ ((m0 + (m0 & CONVERT_MODE_SWAP ? -m1 : m1)) & CONVERT_MODE_ROTATE_MASK) |
+ ((m0 ^ m1) & CONVERT_MODE_SWAP);
+}
+
+
+// This returns a conversion mode that converts an arbitrary 32-bit format
+// specified by bitmap_info to RGBA.
+static convert_mode_t
+convert_mode_to_rgba (CGBitmapInfo bitmap_info)
+{
+ // Former default: kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little
+ // i.e. BGRA
+ // red = 0x00FF0000;
+ // green = 0x0000FF00;
+ // blue = 0x000000FF;
+
+ // RGBA: kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big
+
+ CGImageAlphaInfo alpha_info =
+ (CGImageAlphaInfo)(bitmap_info & kCGBitmapAlphaInfoMask);
+
+ Assert (! (bitmap_info & kCGBitmapFloatComponents),
+ "kCGBitmapFloatComponents unsupported");
+ Assert (alpha_info != kCGImageAlphaOnly, "kCGImageAlphaOnly not supported");
+
+ convert_mode_t rot = alpha_info == kCGImageAlphaFirst ||
+ alpha_info == kCGImageAlphaPremultipliedFirst ||
+ alpha_info == kCGImageAlphaNoneSkipFirst ?
+ 3 : 0;
+
+ CGBitmapInfo byte_order = bitmap_info & kCGBitmapByteOrderMask;
+
+ Assert (byte_order == kCGBitmapByteOrder32Little ||
+ byte_order == kCGBitmapByteOrder32Big,
+ "byte order not supported");
+
+ convert_mode_t swap = byte_order == kCGBitmapByteOrder32Little ?
+ CONVERT_MODE_SWAP : 0;
+ if (swap)
+ rot = CONVERT_MODE_ROTATE_MASK & -rot;
+ return swap | rot;
+}
+
+
+union color_bytes
+{
+ uint32_t pixel;
+ uint8_t bytes[4];
+};
+
+
+static void
+query_color_float (Display *dpy, unsigned long pixel, CGFloat *rgba)
+{
+ JWXYZ_QUERY_COLOR (dpy, pixel, (CGFloat)1, rgba);
+}
+
+
+extern const struct jwxyz_vtbl quartz_vtbl;
+
+Display *
+jwxyz_quartz_make_display (Window w)
+{
+ CGContextRef cgc = w->cgc;
+
+ Display *d = (Display *) calloc (1, sizeof(*d));
+ d->vtbl = &quartz_vtbl;
+
+ d->bitmap_info = CGBitmapContextGetBitmapInfo (cgc);
+
+# if 0
+ // Tests for the image conversion modes.
+ {
+ const uint32_t key = 0x04030201;
+# ifdef __LITTLE_ENDIAN__
+ assert (convert_px (key, 0) == key);
+ assert (convert_px (key, 1) == 0x03020104);
+ assert (convert_px (key, 3) == 0x01040302);
+ assert (convert_px (key, 4) == 0x01020304);
+ assert (convert_px (key, 5) == 0x04010203);
+# endif
+ for (unsigned i = 0; i != 8; ++i) {
+ assert (convert_px(convert_px(key, i), convert_mode_invert(i)) == key);
+ assert (convert_mode_invert(convert_mode_invert(i)) == i);
+ }
+
+ for (unsigned i = 0; i != 8; ++i) {
+ for (unsigned j = 0; j != 8; ++j)
+ assert (convert_px(convert_px(key, i), j) ==
+ convert_px(key, convert_mode_merge(i, j)));
+ }
+ }
+# endif
+
+ Visual *v = &d->visual;
+ v->class = TrueColor;
+
+ union color_bytes color;
+ convert_mode_t mode =
+ convert_mode_invert (convert_mode_to_rgba (d->bitmap_info));
+ unsigned long masks[4];
+ for (unsigned i = 0; i != 4; ++i) {
+ color.pixel = 0;
+ color.bytes[i] = 0xff;
+ masks[i] = convert_px (color.pixel, mode);
+ }
+ v->red_mask = masks[0];
+ v->green_mask = masks[1];
+ v->blue_mask = masks[2];
+ v->alpha_mask = masks[3];
+
+ CGBitmapInfo byte_order = d->bitmap_info & kCGBitmapByteOrderMask;
+ Assert ( ! (d->bitmap_info & kCGBitmapFloatComponents) &&
+ (byte_order == kCGBitmapByteOrder32Little ||
+ byte_order == kCGBitmapByteOrder32Big),
+ "invalid bits per channel");
+
+ d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
+
+ d->window_background = BlackPixel(d,0);
+
+ d->main_window = w;
+
+ Assert (cgc, "no CGContext");
+ return d;
+}
+
+void
+jwxyz_quartz_free_display (Display *dpy)
+{
+ jwxyz_sources_free (dpy->timers_data);
+
+ free (dpy);
+}
+
+
+/* Call this after any modification to the bits on a Pixmap or Window.
+ Most Pixmaps are used frequently as sources and infrequently as
+ destinations, so it pays to cache the data as a CGImage as needed.
+ */
+void
+invalidate_drawable_cache (Drawable d)
+{
+ if (d && d->cgi) {
+ CGImageRelease (d->cgi);
+ d->cgi = 0;
+ }
+}
+
+
+/* Call this when the View changes size or position.
+ */
+void
+jwxyz_window_resized (Display *dpy)
+{
+ Window w = dpy->main_window;
+
+# ifndef USE_IPHONE
+ // Figure out which screen the window is currently on.
+ {
+ int wx, wy;
+ XTranslateCoordinates (dpy, w, NULL, 0, 0, &wx, &wy, NULL);
+ CGPoint p;
+ p.x = wx;
+ p.y = wy;
+ CGDisplayCount n;
+ dpy->cgdpy = 0;
+ CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
+ // Auuugh!
+ if (! dpy->cgdpy) {
+ p.x = p.y = 0;
+ CGGetDisplaysWithPoint (p, 1, &dpy->cgdpy, &n);
+ }
+ Assert (dpy->cgdpy, "unable to find CGDisplay");
+ }
+# endif // USE_IPHONE
+
+/*
+ {
+ // Figure out this screen's colorspace, and use that for every CGImage.
+ //
+ CMProfileRef profile = 0;
+ CMGetProfileByAVID ((CMDisplayIDType) dpy->cgdpy, &profile);
+ Assert (profile, "unable to find colorspace profile");
+ dpy->colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
+ Assert (dpy->colorspace, "unable to find colorspace");
+ }
+ */
+
+ // WTF? It's faster if we *do not* use the screen's colorspace!
+ //
+ dpy->colorspace = CGColorSpaceCreateDeviceRGB();
+
+ invalidate_drawable_cache (w);
+}
+
+
+void
+jwxyz_flush_context (Display *dpy)
+{
+ // CGContextSynchronize is another possibility.
+ CGContextFlush(dpy->main_window->cgc);
+}
+
+static jwxyz_sources_data *
+display_sources_data (Display *dpy)
+{
+ return dpy->timers_data;
+}
+
+
+static Window
+root (Display *dpy)
+{
+ return dpy->main_window;
+}
+
+static Visual *
+visual (Display *dpy)
+{
+ return &dpy->visual;
+}
+
+
+void
+set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
+ unsigned int depth, Bool alpha_allowed_p, Bool fill_p)
+{
+ jwxyz_validate_pixel (dpy, argb, depth, alpha_allowed_p);
+ if (depth == 1) {
+ if (fill_p)
+ CGContextSetGrayFillColor (cgc, (argb ? 1.0 : 0.0), 1.0);
+ else
+ CGContextSetGrayStrokeColor (cgc, (argb ? 1.0 : 0.0), 1.0);
+ } else {
+ CGFloat rgba[4];
+ query_color_float (dpy, argb, rgba);
+ if (fill_p)
+ CGContextSetRGBFillColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
+ else
+ CGContextSetRGBStrokeColor (cgc, rgba[0], rgba[1], rgba[2], rgba[3]);
+ }
+}
+
+static void
+set_line_mode (CGContextRef cgc, XGCValues *gcv)
+{
+ CGContextSetLineWidth (cgc, gcv->line_width ? gcv->line_width : 1);
+ CGContextSetLineJoin (cgc,
+ gcv->join_style == JoinMiter ? kCGLineJoinMiter :
+ gcv->join_style == JoinRound ? kCGLineJoinRound :
+ kCGLineJoinBevel);
+ CGContextSetLineCap (cgc,
+ gcv->cap_style == CapNotLast ? kCGLineCapButt :
+ gcv->cap_style == CapButt ? kCGLineCapButt :
+ gcv->cap_style == CapRound ? kCGLineCapRound :
+ kCGLineCapSquare);
+}
+
+static void
+set_clip_mask (Drawable d, GC gc)
+{
+ Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
+
+ Pixmap p = gc->gcv.clip_mask;
+ if (!p) return;
+ Assert (p->type == PIXMAP, "not a pixmap");
+
+ XRectangle wr = d->frame;
+ CGRect to;
+ to.origin.x = wr.x + gc->gcv.clip_x_origin;
+ to.origin.y = wr.y + wr.height - gc->gcv.clip_y_origin
+ - p->frame.height;
+ to.size.width = p->frame.width;
+ to.size.height = p->frame.height;
+
+ CGContextClipToMask (d->cgc, to, gc->clip_mask);
+}
+
+
+/* Pushes a GC context; sets BlendMode and ClipMask.
+ */
+void
+push_gc (Drawable d, GC gc)
+{
+ CGContextRef cgc = d->cgc;
+ CGContextSaveGState (cgc);
+
+ switch (gc->gcv.function) {
+ case GXset:
+ case GXclear:
+ case GXcopy:/*CGContextSetBlendMode (cgc, kCGBlendModeNormal);*/ break;
+ case GXxor: CGContextSetBlendMode (cgc, kCGBlendModeDifference); break;
+ case GXor: CGContextSetBlendMode (cgc, kCGBlendModeLighten); break;
+ case GXand: CGContextSetBlendMode (cgc, kCGBlendModeDarken); break;
+ default: Assert(0, "unknown gcv function"); break;
+ }
+
+ if (gc->gcv.clip_mask)
+ set_clip_mask (d, gc);
+}
+
+
+/* Pushes a GC context; sets BlendMode, ClipMask, Fill, and Stroke colors.
+ */
+void
+push_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color,
+ Bool antialias_p, Bool fill_p)
+{
+ push_gc (d, gc);
+
+ int depth = gc->depth;
+ switch (gc->gcv.function) {
+ case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
+ case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
+ }
+
+ CGContextRef cgc = d->cgc;
+ set_color (dpy, cgc, color, depth, gc->gcv.alpha_allowed_p, fill_p);
+ CGContextSetShouldAntialias (cgc, antialias_p);
+}
+
+
+/* Pushes a GC context; sets Fill and Stroke colors to the foreground color.
+ */
+static void
+push_fg_gc (Display *dpy, Drawable d, GC gc, Bool fill_p)
+{
+ push_color_gc (dpy, d, gc, gc->gcv.foreground, gc->gcv.antialias_p, fill_p);
+}
+
+static Bool
+bitmap_context_p (Drawable d)
+{
+ return True;
+}
+
+
+
+/* You've got to be fucking kidding me!
+
+ It is *way* faster to draw points by creating and drawing a 1x1 CGImage
+ with repeated calls to CGContextDrawImage than it is to make a single
+ call to CGContextFillRects() with a list of 1x1 rectangles!
+
+ I still wouldn't call it *fast*, however...
+ */
+#define XDRAWPOINTS_IMAGES
+
+/* Update, 2012: Kurt Revis <krevis@snoize.com> points out that diddling
+ the bitmap data directly is faster. This only works on Pixmaps, though,
+ not Windows. (Fortunately, on iOS, the Window is really a Pixmap.)
+ */
+#define XDRAWPOINTS_CGDATA
+
+static int
+DrawPoints (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int count, int mode)
+{
+ int i;
+ XRectangle wr = d->frame;
+
+# ifdef XDRAWPOINTS_CGDATA
+
+ if (bitmap_context_p (d))
+ {
+ CGContextRef cgc = d->cgc;
+ void *data = CGBitmapContextGetData (cgc);
+ size_t bpr = CGBitmapContextGetBytesPerRow (cgc);
+ size_t w = CGBitmapContextGetWidth (cgc);
+ size_t h = CGBitmapContextGetHeight (cgc);
+
+ Assert (data, "no bitmap data in Drawable");
+
+ unsigned long argb = gc->gcv.foreground;
+ jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
+ if (gc->depth == 1)
+ argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
+
+ CGFloat x0 = wr.x;
+ CGFloat y0 = wr.y; // Y axis is refreshingly not flipped.
+
+ // It's uglier, but faster, to hoist the conditional out of the loop.
+ if (mode == CoordModePrevious) {
+ CGFloat x = x0, y = y0;
+ for (i = 0; i < count; i++, points++) {
+ x += points->x;
+ y += points->y;
+
+ if (x >= 0 && x < w && y >= 0 && y < h) {
+ unsigned int *p = (unsigned int *)
+ ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+ *p = (unsigned int) argb;
+ }
+ }
+ } else {
+ for (i = 0; i < count; i++, points++) {
+ CGFloat x = x0 + points->x;
+ CGFloat y = y0 + points->y;
+
+ if (x >= 0 && x < w && y >= 0 && y < h) {
+ unsigned int *p = (unsigned int *)
+ ((char *) data + (size_t) y * bpr + (size_t) x * 4);
+ *p = (unsigned int) argb;
+ }
+ }
+ }
+
+ } else /* d->type == WINDOW */
+
+# endif /* XDRAWPOINTS_CGDATA */
+ {
+ push_fg_gc (dpy, d, gc, YES);
+
+# ifdef XDRAWPOINTS_IMAGES
+
+ unsigned long argb = gc->gcv.foreground;
+ jwxyz_validate_pixel (dpy, argb, gc->depth, gc->gcv.alpha_allowed_p);
+ if (gc->depth == 1)
+ argb = (gc->gcv.foreground ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
+
+ CGDataProviderRef prov = CGDataProviderCreateWithData (NULL, &argb, 4,
+ NULL);
+ CGImageRef cgi = CGImageCreate (1, 1,
+ 8, 32, 4,
+ dpy->colorspace,
+ /* Host-ordered, since we're using the
+ address of an int as the color data. */
+ dpy->bitmap_info,
+ prov,
+ NULL, /* decode[] */
+ NO, /* interpolate */
+ kCGRenderingIntentDefault);
+ CGDataProviderRelease (prov);
+
+ CGContextRef cgc = d->cgc;
+ CGRect rect;
+ rect.size.width = rect.size.height = 1;
+ for (i = 0; i < count; i++) {
+ if (i > 0 && mode == CoordModePrevious) {
+ rect.origin.x += points->x;
+ rect.origin.x -= points->y;
+ } else {
+ rect.origin.x = wr.x + points->x;
+ rect.origin.y = wr.y + wr.height - points->y - 1;
+ }
+
+ //Assert(CGImageGetColorSpace (cgi) == dpy->colorspace,"bad colorspace");
+ CGContextDrawImage (cgc, rect, cgi);
+ points++;
+ }
+
+ CGImageRelease (cgi);
+
+# else /* ! XDRAWPOINTS_IMAGES */
+
+ CGRect *rects = (CGRect *) malloc (count * sizeof(CGRect));
+ CGRect *r = rects;
+
+ for (i = 0; i < count; i++) {
+ r->size.width = r->size.height = 1;
+ if (i > 0 && mode == CoordModePrevious) {
+ r->origin.x = r[-1].origin.x + points->x;
+ r->origin.y = r[-1].origin.x - points->y;
+ } else {
+ r->origin.x = wr.origin.x + points->x;
+ r->origin.y = wr.origin.y + wr.size.height - points->y;
+ }
+ points++;
+ r++;
+ }
+
+ CGContextFillRects (d->cgc, rects, count);
+ free (rects);
+
+# endif /* ! XDRAWPOINTS_IMAGES */
+
+ pop_gc (d, gc);
+ }
+
+ invalidate_drawable_cache (d);
+
+ return 0;
+}
+
+
+CGPoint
+map_point (Drawable d, int x, int y)
+{
+ const XRectangle *wr = &d->frame;
+ CGPoint p;
+ p.x = wr->x + x;
+ p.y = wr->y + wr->height - y;
+ return p;
+}
+
+
+static void
+adjust_point_for_line (GC gc, CGPoint *p)
+{
+ // Here's the authoritative discussion on how X draws lines:
+ // http://www.x.org/releases/current/doc/xproto/x11protocol.html#requests:CreateGC:line-width
+ if (gc->gcv.line_width <= 1) {
+ /* Thin lines are "drawn using an unspecified, device-dependent
+ algorithm", but seriously though, Bresenham's algorithm. Bresenham's
+ algorithm runs to and from pixel centers.
+
+ There's a few screenhacks (Maze, at the very least) that set line_width
+ to 1 when it probably should be set to 0, so it's line_width <= 1
+ instead of < 1.
+ */
+ p->x += 0.5;
+ p->y -= 0.5;
+ } else {
+ /* Thick lines OTOH run from the upper-left corners of pixels. This means
+ that a horizontal thick line of width 1 straddles two scan lines.
+ Aliasing requires one of these scan lines be chosen; the following
+ nudges the point so that the right choice is made. */
+ p->y -= 1e-3;
+ }
+}
+
+
+static CGPoint
+point_for_line (Drawable d, GC gc, int x, int y)
+{
+ CGPoint result = map_point (d, x, y);
+ adjust_point_for_line (gc, &result);
+ return result;
+}
+
+
+static int
+DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
+ int mode)
+{
+ int i;
+ CGPoint p;
+ push_fg_gc (dpy, d, gc, NO);
+
+ CGContextRef cgc = d->cgc;
+
+ set_line_mode (cgc, &gc->gcv);
+
+ // if the first and last points coincide, use closepath to get
+ // the proper line-joining.
+ BOOL closed_p = (points[0].x == points[count-1].x &&
+ points[0].y == points[count-1].y);
+ if (closed_p) count--;
+
+ p = point_for_line(d, gc, points->x, points->y);
+ points++;
+ CGContextBeginPath (cgc);
+ CGContextMoveToPoint (cgc, p.x, p.y);
+ for (i = 1; i < count; i++) {
+ if (mode == CoordModePrevious) {
+ p.x += points->x;
+ p.y -= points->y;
+ } else {
+ p = point_for_line(d, gc, points->x, points->y);
+ }
+ CGContextAddLineToPoint (cgc, p.x, p.y);
+ points++;
+ }
+ if (closed_p) CGContextClosePath (cgc);
+ CGContextStrokePath (cgc);
+ pop_gc (d, gc);
+ invalidate_drawable_cache (d);
+ return 0;
+}
+
+
+static int
+DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
+{
+ int i;
+
+ CGContextRef cgc = d->cgc;
+
+ push_fg_gc (dpy, d, gc, NO);
+ set_line_mode (cgc, &gc->gcv);
+ CGContextBeginPath (cgc);
+ for (i = 0; i < count; i++) {
+ // when drawing a zero-length line, obey line-width and cap-style.
+ if (segments->x1 == segments->x2 && segments->y1 == segments->y2) {
+ int w = gc->gcv.line_width;
+ int x1 = segments->x1 - w/2;
+ int y1 = segments->y1 - w/2;
+ if (gc->gcv.line_width > 1 && gc->gcv.cap_style == CapRound)
+ XFillArc (dpy, d, gc, x1, y1, w, w, 0, 360*64);
+ else {
+ if (!w)
+ w = 1; // Actually show zero-length lines.
+ XFillRectangle (dpy, d, gc, x1, y1, w, w);
+ }
+ } else {
+ CGPoint p = point_for_line (d, gc, segments->x1, segments->y1);
+ CGContextMoveToPoint (cgc, p.x, p.y);
+ p = point_for_line (d, gc, segments->x2, segments->y2);
+ CGContextAddLineToPoint (cgc, p.x, p.y);
+ }
+
+ segments++;
+ }
+ CGContextStrokePath (cgc);
+ pop_gc (d, gc);
+ invalidate_drawable_cache (d);
+ return 0;
+}
+
+
+static int
+ClearWindow (Display *dpy, Window win)
+{
+ Assert (win && win->type == WINDOW, "not a window");
+ XRectangle wr = win->frame;
+ return XClearArea (dpy, win, 0, 0, wr.width, wr.height, 0);
+}
+
+static unsigned long *
+window_background (Display *dpy)
+{
+ return &dpy->window_background;
+}
+
+static void
+fill_rects (Display *dpy, Drawable d, GC gc,
+ const XRectangle *rectangles, unsigned long nrectangles,
+ unsigned long pixel)
+{
+ Assert (!gc || gc->depth == jwxyz_drawable_depth (d), "depth mismatch");
+
+ CGContextRef cgc = d->cgc;
+
+ Bool fast_fill_p =
+ bitmap_context_p (d) &&
+ (!gc || (gc->gcv.function == GXcopy &&
+ !gc->gcv.alpha_allowed_p &&
+ !gc->gcv.clip_mask));
+
+ if (!fast_fill_p) {
+ if (gc)
+ push_color_gc (dpy, d, gc, pixel, gc->gcv.antialias_p, YES);
+ else
+ set_color (dpy, d->cgc, pixel, jwxyz_drawable_depth (d), NO, YES);
+ }
+
+ for (unsigned i = 0; i != nrectangles; ++i) {
+
+ int x = rectangles[i].x;
+ int y = rectangles[i].y;
+ unsigned long width = rectangles[i].width;
+ unsigned long height = rectangles[i].height;
+
+ if (fast_fill_p) {
+ long // negative_int > unsigned_int == 1
+ dw = CGBitmapContextGetWidth (cgc),
+ dh = CGBitmapContextGetHeight (cgc);
+
+ if (x >= dw || y >= dh)
+ continue;
+
+ if (x < 0) {
+ width += x;
+ x = 0;
+ }
+
+ if (y < 0) {
+ height += y;
+ y = 0;
+ }
+
+ if (width <= 0 || height <= 0)
+ continue;
+
+ unsigned long max_width = dw - x;
+ if (width > max_width)
+ width = max_width;
+ unsigned long max_height = dh - y;
+ if (height > max_height)
+ height = max_height;
+
+ if (jwxyz_drawable_depth (d) == 1)
+ pixel = pixel ? WhitePixel(dpy, 0) : BlackPixel(dpy, 0);
+
+ size_t dst_bytes_per_row = CGBitmapContextGetBytesPerRow (d->cgc);
+ void *dst = SEEK_XY (CGBitmapContextGetData (d->cgc),
+ dst_bytes_per_row, x, y);
+
+ Assert(sizeof(wchar_t) == 4, "somebody changed the ABI");
+ while (height) {
+ // Would be nice if Apple used SSE/NEON in wmemset. Maybe someday.
+ wmemset (dst, (wchar_t) pixel, width);
+ --height;
+ dst = (char *) dst + dst_bytes_per_row;
+ }
+
+ } else {
+ CGRect r;
+ r.origin = map_point (d, x, y);
+ r.origin.y -= height;
+ r.size.width = width;
+ r.size.height = height;
+ CGContextFillRect (cgc, r);
+ }
+ }
+
+ if (!fast_fill_p && gc)
+ pop_gc (d, gc);
+ invalidate_drawable_cache (d);
+}
+
+
+static int
+FillPolygon (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int npoints, int shape, int mode)
+{
+ XRectangle wr = d->frame;
+ int i;
+ push_fg_gc (dpy, d, gc, YES);
+ CGContextRef cgc = d->cgc;
+ CGContextBeginPath (cgc);
+ float x = 0, y = 0;
+ for (i = 0; i < npoints; i++) {
+ if (i > 0 && mode == CoordModePrevious) {
+ x += points[i].x;
+ y -= points[i].y;
+ } else {
+ x = wr.x + points[i].x;
+ y = wr.y + wr.height - points[i].y;
+ }
+
+ if (i == 0)
+ CGContextMoveToPoint (cgc, x, y);
+ else
+ CGContextAddLineToPoint (cgc, x, y);
+ }
+ CGContextClosePath (cgc);
+ if (gc->gcv.fill_rule == EvenOddRule)
+ CGContextEOFillPath (cgc);
+ else
+ CGContextFillPath (cgc);
+ pop_gc (d, gc);
+ invalidate_drawable_cache (d);
+ return 0;
+}
+
+#define radians(DEG) ((DEG) * M_PI / 180.0)
+#define degrees(RAD) ((RAD) * 180.0 / M_PI)
+
+static int
+draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ XRectangle wr = d->frame;
+ CGRect bound;
+ bound.origin.x = wr.x + x;
+ bound.origin.y = wr.y + wr.height - y - (int)height;
+ bound.size.width = width;
+ bound.size.height = height;
+
+ CGPoint ctr;
+ ctr.x = bound.origin.x + bound.size.width /2;
+ ctr.y = bound.origin.y + bound.size.height/2;
+
+ float r1 = radians (angle1/64.0);
+ float r2 = radians (angle2/64.0) + r1;
+ BOOL clockwise = angle2 < 0;
+ BOOL closed_p = (angle2 >= 360*64 || angle2 <= -360*64);
+
+ push_fg_gc (dpy, d, gc, fill_p);
+
+ CGContextRef cgc = d->cgc;
+ CGContextBeginPath (cgc);
+
+ CGContextSaveGState(cgc);
+ CGContextTranslateCTM (cgc, ctr.x, ctr.y);
+ CGContextScaleCTM (cgc, width/2.0, height/2.0);
+ if (fill_p)
+ CGContextMoveToPoint (cgc, 0, 0);
+
+ CGContextAddArc (cgc, 0.0, 0.0, 1, r1, r2, clockwise);
+ CGContextRestoreGState (cgc); // restore before stroke, for line width
+
+ if (closed_p)
+ CGContextClosePath (cgc); // for proper line joining
+
+ if (fill_p) {
+ CGContextFillPath (cgc);
+ } else {
+ set_line_mode (cgc, &gc->gcv);
+ CGContextStrokePath (cgc);
+ }
+
+ pop_gc (d, gc);
+ invalidate_drawable_cache (d);
+ return 0;
+}
+
+
+static XGCValues *
+gc_gcv (GC gc)
+{
+ return &gc->gcv;
+}
+
+
+static unsigned int
+gc_depth (GC gc)
+{
+ return gc->depth;
+}
+
+
+static GC
+CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
+{
+ struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
+ gc->depth = jwxyz_drawable_depth (d);
+
+ jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
+ XChangeGC (dpy, gc, mask, xgcv);
+ return gc;
+}
+
+
+static int
+FreeGC (Display *dpy, GC gc)
+{
+ if (gc->gcv.font)
+ XUnloadFont (dpy, gc->gcv.font);
+
+ Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
+
+ if (gc->gcv.clip_mask) {
+ XFreePixmap (dpy, gc->gcv.clip_mask);
+ CGImageRelease (gc->clip_mask);
+ }
+ free (gc);
+ return 0;
+}
+
+
+static void
+flipbits (unsigned const char *in, unsigned char *out, int length)
+{
+ static const unsigned char table[256] = {
+ 0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0,
+ 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
+ 0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8,
+ 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
+ 0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4,
+ 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
+ 0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC,
+ 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
+ 0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2,
+ 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
+ 0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA,
+ 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
+ 0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6,
+ 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
+ 0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE,
+ 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
+ 0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1,
+ 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
+ 0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9,
+ 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
+ 0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5,
+ 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
+ 0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED,
+ 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
+ 0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3,
+ 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
+ 0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB,
+ 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
+ 0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7,
+ 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
+ 0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF,
+ 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF
+ };
+ while (length-- > 0)
+ *out++ = table[*in++];
+}
+
+
+static int
+PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h)
+{
+ XRectangle wr = d->frame;
+
+ Assert (gc, "no GC");
+ Assert ((w < 65535), "improbably large width");
+ Assert ((h < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
+ Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
+
+ // Clip width and height to the bounds of the Drawable
+ //
+ if (dest_x + (int)w > wr.width) {
+ if (dest_x > wr.width)
+ return 0;
+ w = wr.width - dest_x;
+ }
+ if (dest_y + (int)h > wr.height) {
+ if (dest_y > wr.height)
+ return 0;
+ h = wr.height - dest_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ // Clip width and height to the bounds of the XImage
+ //
+ if (src_x + w > ximage->width) {
+ if (src_x > ximage->width)
+ return 0;
+ w = ximage->width - src_x;
+ }
+ if (src_y + h > ximage->height) {
+ if (src_y > ximage->height)
+ return 0;
+ h = ximage->height - src_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ CGContextRef cgc = d->cgc;
+
+ if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
+ return 0;
+
+ int bpl = ximage->bytes_per_line;
+ int bpp = ximage->bits_per_pixel;
+ int bsize = bpl * h;
+ char *data = ximage->data;
+
+ CGRect r;
+ r.origin.x = wr.x + dest_x;
+ r.origin.y = wr.y + wr.height - dest_y - (int)h;
+ r.size.width = w;
+ r.size.height = h;
+
+ if (bpp == 32) {
+
+ /* Take advantage of the fact that it's ok for (bpl != w * bpp)
+ to create a CGImage from a sub-rectagle of the XImage.
+ */
+ data += (src_y * bpl) + (src_x * 4);
+ CGDataProviderRef prov =
+ CGDataProviderCreateWithData (NULL, data, bsize, NULL);
+
+ CGImageRef cgi = CGImageCreate (w, h,
+ bpp/4, bpp, bpl,
+ dpy->colorspace,
+ dpy->bitmap_info,
+ prov,
+ NULL, /* decode[] */
+ NO, /* interpolate */
+ kCGRenderingIntentDefault);
+ CGDataProviderRelease (prov);
+ //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
+ CGContextDrawImage (cgc, r, cgi);
+ CGImageRelease (cgi);
+
+ } else { // (bpp == 1)
+
+ /* To draw a 1bpp image, we use it as a mask and fill two rectangles.
+
+ #### However, the bit order within a byte in a 1bpp XImage is
+ the wrong way around from what Quartz expects, so first we
+ have to copy the data to reverse it. Shit! Maybe it
+ would be worthwhile to go through the hacks and #ifdef
+ each one that diddles 1bpp XImage->data directly...
+ */
+ Assert ((src_x % 8) == 0,
+ "XPutImage with non-byte-aligned 1bpp X offset not implemented");
+
+ data += (src_y * bpl) + (src_x / 8); // move to x,y within the data
+ unsigned char *flipped = (unsigned char *) malloc (bsize);
+
+ flipbits ((unsigned char *) data, flipped, bsize);
+
+ CGDataProviderRef prov =
+ CGDataProviderCreateWithData (NULL, flipped, bsize, NULL);
+ CGImageRef mask = CGImageMaskCreate (w, h,
+ 1, bpp, bpl,
+ prov,
+ NULL, /* decode[] */
+ NO); /* interpolate */
+ push_fg_gc (dpy, d, gc, YES);
+
+ CGContextFillRect (cgc, r); // foreground color
+ CGContextClipToMask (cgc, r, mask);
+ set_color (dpy, cgc, gc->gcv.background, gc->depth, NO, YES);
+ CGContextFillRect (cgc, r); // background color
+ pop_gc (d, gc);
+
+ free (flipped);
+ CGDataProviderRelease (prov);
+ CGImageRelease (mask);
+ }
+
+ invalidate_drawable_cache (d);
+
+ return 0;
+}
+
+
+static XImage *
+GetSubImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format,
+ XImage *image, int dest_x, int dest_y)
+{
+ const unsigned char *data = 0;
+ size_t depth, ibpp, ibpl;
+ convert_mode_t mode;
+
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((x < 65535 && x > -65535), "improbably large x");
+ Assert ((y < 65535 && y > -65535), "improbably large y");
+
+ CGContextRef cgc = d->cgc;
+
+ {
+ depth = jwxyz_drawable_depth (d);
+ mode = convert_mode_to_rgba (dpy->bitmap_info);
+ ibpp = CGBitmapContextGetBitsPerPixel (cgc);
+ ibpl = CGBitmapContextGetBytesPerRow (cgc);
+ data = CGBitmapContextGetData (cgc);
+ Assert (data, "CGBitmapContextGetData failed");
+ }
+
+ // data points at (x,y) with ibpl rowstride. ignore x,y from now on.
+ data += (y * ibpl) + (x * (ibpp/8));
+
+ format = (depth == 1 ? XYPixmap : ZPixmap);
+
+ int obpl = image->bytes_per_line;
+
+ /* both PPC and Intel use word-ordered ARGB frame buffers, which
+ means that on Intel it is BGRA when viewed by bytes (And BGR
+ when using 24bpp packing).
+
+ BUT! Intel-64 stores alpha at the other end! 32bit=RGBA, 64bit=ARGB.
+ The NSAlphaFirstBitmapFormat bit in bitmapFormat seems to be the
+ indicator of this latest kink.
+ */
+ int xx, yy;
+ if (depth == 1) {
+ const unsigned char *iline = data;
+ for (yy = 0; yy < height; yy++) {
+
+ const unsigned char *iline2 = iline;
+ for (xx = 0; xx < width; xx++) {
+
+ iline2++; // ignore R or A or A or B
+ iline2++; // ignore G or B or R or G
+ unsigned char r = *iline2++; // use B or G or G or R
+ if (ibpp == 32) iline2++; // ignore A or R or B or A
+
+ XPutPixel (image, xx + dest_x, yy + dest_y, (r ? 1 : 0));
+ }
+ iline += ibpl;
+ }
+ } else {
+ const unsigned char *iline = data;
+ unsigned char *oline = (unsigned char *) image->data + dest_y * obpl +
+ dest_x * 4;
+
+ mode = convert_mode_merge (mode,
+ convert_mode_invert (
+ convert_mode_to_rgba (dpy->bitmap_info)));
+
+ for (yy = 0; yy < height; yy++) {
+
+ convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
+
+ oline += obpl;
+ iline += ibpl;
+ }
+ }
+
+ return image;
+}
+
+
+
+/* Returns a transformation matrix to do rotation as per the provided
+ EXIF "Orientation" value.
+ */
+static CGAffineTransform
+exif_rotate (int rot, CGSize rect)
+{
+ CGAffineTransform trans = CGAffineTransformIdentity;
+ switch (rot) {
+ case 2: // flip horizontal
+ trans = CGAffineTransformMakeTranslation (rect.width, 0);
+ trans = CGAffineTransformScale (trans, -1, 1);
+ break;
+
+ case 3: // rotate 180
+ trans = CGAffineTransformMakeTranslation (rect.width, rect.height);
+ trans = CGAffineTransformRotate (trans, M_PI);
+ break;
+
+ case 4: // flip vertical
+ trans = CGAffineTransformMakeTranslation (0, rect.height);
+ trans = CGAffineTransformScale (trans, 1, -1);
+ break;
+
+ case 5: // transpose (UL-to-LR axis)
+ trans = CGAffineTransformMakeTranslation (rect.height, rect.width);
+ trans = CGAffineTransformScale (trans, -1, 1);
+ trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
+ break;
+
+ case 6: // rotate 90
+ trans = CGAffineTransformMakeTranslation (0, rect.width);
+ trans = CGAffineTransformRotate (trans, 3 * M_PI / 2);
+ break;
+
+ case 7: // transverse (UR-to-LL axis)
+ trans = CGAffineTransformMakeScale (-1, 1);
+ trans = CGAffineTransformRotate (trans, M_PI / 2);
+ break;
+
+ case 8: // rotate 270
+ trans = CGAffineTransformMakeTranslation (rect.height, 0);
+ trans = CGAffineTransformRotate (trans, M_PI / 2);
+ break;
+
+ default:
+ break;
+ }
+
+ return trans;
+}
+
+
+void
+jwxyz_draw_NSImage_or_CGImage (Display *dpy, Drawable d,
+ Bool nsimg_p, void *img_arg,
+ XRectangle *geom_ret, int exif_rotation)
+{
+ CGImageRef cgi;
+# ifndef USE_IPHONE
+ CGImageSourceRef cgsrc;
+# endif // USE_IPHONE
+ NSSize imgr;
+
+ CGContextRef cgc = d->cgc;
+
+ if (nsimg_p) {
+
+ NSImage *nsimg = (NSImage *) img_arg;
+ imgr = [nsimg size];
+
+# ifndef USE_IPHONE
+ // convert the NSImage to a CGImage via the toll-free-bridging
+ // of NSData and CFData...
+ //
+ NSData *nsdata = [NSBitmapImageRep
+ TIFFRepresentationOfImageRepsInArray:
+ [nsimg representations]];
+ CFDataRef cfdata = (CFDataRef) nsdata;
+ cgsrc = CGImageSourceCreateWithData (cfdata, NULL);
+ cgi = CGImageSourceCreateImageAtIndex (cgsrc, 0, NULL);
+# else // USE_IPHONE
+ cgi = nsimg.CGImage;
+# endif // USE_IPHONE
+
+ } else {
+ cgi = (CGImageRef) img_arg;
+ imgr.width = CGImageGetWidth (cgi);
+ imgr.height = CGImageGetHeight (cgi);
+ }
+
+ Bool rot_p = (exif_rotation >= 5);
+
+ if (rot_p)
+ imgr = NSMakeSize (imgr.height, imgr.width);
+
+ XRectangle winr = d->frame;
+ float rw = winr.width / imgr.width;
+ float rh = winr.height / imgr.height;
+ float r = (rw < rh ? rw : rh);
+
+ /* If the window is a goofy aspect ratio, take a middle slice of
+ the image instead. */
+ if (winr.width > winr.height * 5 ||
+ winr.width > winr.width * 5) {
+ r *= (winr.width > winr.height
+ ? winr.width / (double) winr.height
+ : winr.height / (double) winr.width);
+ // NSLog (@"weird aspect: scaling by %.1f\n", r);
+ }
+
+ CGRect dst, dst2;
+ dst.size.width = imgr.width * r;
+ dst.size.height = imgr.height * r;
+ dst.origin.x = (winr.width - dst.size.width) / 2;
+ dst.origin.y = (winr.height - dst.size.height) / 2;
+
+ dst2.origin.x = dst2.origin.y = 0;
+ if (rot_p) {
+ dst2.size.width = dst.size.height;
+ dst2.size.height = dst.size.width;
+ } else {
+ dst2.size = dst.size;
+ }
+
+ // Clear the part not covered by the image to background or black.
+ //
+ if (d->type == WINDOW)
+ XClearWindow (dpy, d);
+ else {
+ jwxyz_fill_rect (dpy, d, 0, 0, 0, winr.width, winr.height,
+ jwxyz_drawable_depth (d) == 1 ? 0 : BlackPixel(dpy,0));
+ }
+
+ CGAffineTransform trans =
+ exif_rotate (exif_rotation, rot_p ? dst2.size : dst.size);
+
+ CGContextSaveGState (cgc);
+ CGContextConcatCTM (cgc,
+ CGAffineTransformMakeTranslation (dst.origin.x,
+ dst.origin.y));
+ CGContextConcatCTM (cgc, trans);
+ //Assert (CGImageGetColorSpace (cgi) == dpy->colorspace, "bad colorspace");
+ CGContextDrawImage (cgc, dst2, cgi);
+ CGContextRestoreGState (cgc);
+
+# ifndef USE_IPHONE
+ if (nsimg_p) {
+ CFRelease (cgsrc);
+ CGImageRelease (cgi);
+ }
+# endif // USE_IPHONE
+
+ if (geom_ret) {
+ geom_ret->x = dst.origin.x;
+ geom_ret->y = dst.origin.y;
+ geom_ret->width = dst.size.width;
+ geom_ret->height = dst.size.height;
+ }
+
+ invalidate_drawable_cache (d);
+}
+
+
+XImage *
+jwxyz_png_to_ximage (Display *dpy, Visual *visual,
+ const unsigned char *png_data, unsigned long data_size)
+{
+ NSImage *img = [[NSImage alloc] initWithData:
+ [NSData dataWithBytes:png_data
+ length:data_size]];
+ if (! img) return 0;
+#ifndef USE_IPHONE
+ NSBitmapImageRep *bm = [NSBitmapImageRep
+ imageRepWithData:
+ [NSBitmapImageRep
+ TIFFRepresentationOfImageRepsInArray:
+ [img representations]]];
+ if (!bm) {
+ [img release];
+ return 0;
+ }
+ int width = [img size].width;
+ int height = [img size].height;
+ size_t ibpp = [bm bitsPerPixel];
+ size_t ibpl = [bm bytesPerRow];
+ const unsigned char *data = [bm bitmapData];
+ convert_mode_t mode = (([bm bitmapFormat] & NSAlphaFirstBitmapFormat)
+ ? CONVERT_MODE_ROTATE_MASK
+ : 0);
+#else // USE_IPHONE
+ CGImageRef cgi = [img CGImage];
+ if (!cgi) {
+ [img release];
+ return 0;
+ }
+ int width = CGImageGetWidth (cgi);
+ int height = CGImageGetHeight (cgi);
+ size_t ibpp = 32;
+ size_t ibpl = ibpp/4 * width;
+ unsigned char *data = (unsigned char *) calloc (ibpl, height);
+ const CGBitmapInfo bitmap_info =
+ kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast;
+ CGContextRef cgc =
+ CGBitmapContextCreate (data, width, height,
+ 8, /* bits per component */
+ ibpl, dpy->colorspace,
+ bitmap_info);
+ CGContextDrawImage (cgc, CGRectMake (0, 0, width, height), cgi);
+
+ convert_mode_t mode = convert_mode_to_rgba (bitmap_info);
+
+#endif // USE_IPHONE
+
+ XImage *image = XCreateImage (dpy, visual, 32, ZPixmap, 0, 0,
+ width, height, 8, 0);
+ image->data = (char *) malloc (image->height * image->bytes_per_line);
+
+ // data points at (x,y) with ibpl rowstride.
+
+ int obpl = image->bytes_per_line;
+ const unsigned char *iline = data;
+ unsigned char *oline = (unsigned char *) image->data;
+ int yy;
+ for (yy = 0; yy < height; yy++) {
+ convert_row ((uint32_t *)oline, iline, width, mode, ibpp);
+ oline += obpl;
+ iline += ibpl;
+ }
+
+ [img release];
+
+#ifndef USE_IPHONE
+ // [bm release];
+# else
+ CGContextRelease (cgc);
+ free (data);
+# endif
+
+ return image;
+}
+
+
+Pixmap
+XCreatePixmap (Display *dpy, Drawable d,
+ unsigned int width, unsigned int height, unsigned int depth)
+{
+ if (!dpy) abort();
+ char *data = (char *) malloc (width * height * 4);
+ if (! data) return 0;
+
+ Pixmap p = (Pixmap) calloc (1, sizeof(*p));
+ p->type = PIXMAP;
+ p->frame.width = width;
+ p->frame.height = height;
+ p->pixmap.depth = depth;
+ p->pixmap.cgc_buffer = data;
+
+ /* Quartz doesn't have a 1bpp image type.
+ Used to use 8bpp gray images instead of 1bpp, but some Mac video cards
+ don't support that! So we always use 32bpp, regardless of depth. */
+
+ p->cgc = CGBitmapContextCreate (data, width, height,
+ 8, /* bits per component */
+ width * 4, /* bpl */
+ dpy->colorspace,
+ dpy->bitmap_info);
+ Assert (p->cgc, "could not create CGBitmapContext");
+ return p;
+}
+
+
+int
+XFreePixmap (Display *d, Pixmap p)
+{
+ Assert (p && p->type == PIXMAP, "not a pixmap");
+ invalidate_drawable_cache (p);
+ CGContextRelease (p->cgc);
+ if (p->pixmap.cgc_buffer)
+ free (p->pixmap.cgc_buffer);
+ free (p);
+ return 0;
+}
+
+
+static Pixmap
+copy_pixmap (Display *dpy, Pixmap p)
+{
+ if (!p) return 0;
+ Assert (p->type == PIXMAP, "not a pixmap");
+
+ Pixmap p2 = 0;
+
+ Window root;
+ int x, y;
+ unsigned int width, height, border_width, depth;
+ if (XGetGeometry (dpy, p, &root,
+ &x, &y, &width, &height, &border_width, &depth)) {
+ XGCValues gcv;
+ gcv.function = GXcopy;
+ GC gc = XCreateGC (dpy, p, GCFunction, &gcv);
+ if (gc) {
+ p2 = XCreatePixmap (dpy, p, width, height, depth);
+ if (p2)
+ XCopyArea (dpy, p, p2, gc, 0, 0, width, height, 0, 0);
+ XFreeGC (dpy, gc);
+ }
+ }
+
+ Assert (p2, "could not copy pixmap");
+
+ return p2;
+}
+
+
+// Returns the verbose Unicode name of this character, like "agrave" or
+// "daggerdouble". Used by fontglide debugMetrics.
+//
+char *
+jwxyz_unicode_character_name (Display *dpy, Font fid, unsigned long uc)
+{
+ char *ret = 0;
+ NSFont *nsfont = (NSFont *) jwxyz_native_font (fid);
+ CTFontRef ctfont =
+ CTFontCreateWithName ((CFStringRef) [nsfont fontName],
+ [nsfont pointSize],
+ NULL);
+ Assert (ctfont, "no CTFontRef for UIFont");
+
+ CGGlyph cgglyph;
+ if (CTFontGetGlyphsForCharacters (ctfont, (UniChar *) &uc, &cgglyph, 1)) {
+ CGFontRef cgfont = CTFontCopyGraphicsFont (ctfont, 0);
+ NSString *name = (NSString *) CGFontCopyGlyphNameForGlyph(cgfont, cgglyph);
+ ret = (name ? strdup ([name UTF8String]) : 0);
+ CGFontRelease (cgfont);
+ [name release];
+ }
+
+ CFRelease (ctfont);
+ return ret;
+}
+
+
+static int
+draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, size_t len, int utf8_p)
+{
+ NSString *nsstr = nsstring_from (str, len, utf8_p);
+
+ if (! nsstr) return 1;
+
+ XRectangle wr = d->frame;
+ CGContextRef cgc = d->cgc;
+
+ unsigned long argb = gc->gcv.foreground;
+ if (gc->depth == 1) argb = (argb ? WhitePixel(dpy,0) : BlackPixel(dpy,0));
+ CGFloat rgba[4];
+ query_color_float (dpy, argb, rgba);
+ NSColor *fg = [NSColor colorWithDeviceRed:rgba[0]
+ green:rgba[1]
+ blue:rgba[2]
+ alpha:rgba[3]];
+
+ if (!gc->gcv.font) {
+ Assert (0, "no font");
+ return 1;
+ }
+
+ /* This crashes on iOS 5.1 because NSForegroundColorAttributeName,
+ NSFontAttributeName, and NSAttributedString are only present on iOS 6
+ and later. We could resurrect the Quartz code from v5.29 and do a
+ runtime conditional on that, but that would be a pain in the ass.
+ Probably time to just make iOS 6 a requirement.
+ */
+
+ NSDictionary *attr =
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ (NSFont *) jwxyz_native_font (gc->gcv.font), NSFontAttributeName,
+ fg, NSForegroundColorAttributeName,
+ nil];
+
+ // Don't understand why we have to do both set_color and
+ // NSForegroundColorAttributeName, but we do.
+ //
+ set_color (dpy, cgc, argb, 32, NO, YES);
+
+ NSAttributedString *astr = [[NSAttributedString alloc]
+ initWithString:nsstr
+ attributes:attr];
+ CTLineRef dl = CTLineCreateWithAttributedString (
+ (__bridge CFAttributedStringRef) astr);
+
+ // Not sure why this is necessary, but xoff is positive when the first
+ // character on the line has a negative lbearing. Without this, the
+ // string is rendered with the first ink at 0 instead of at lbearing.
+ // I have not seen xoff be negative, so I'm not sure if that can happen.
+ //
+ // Test case: "Combining Double Tilde" U+0360 (\315\240) followed by
+ // a letter.
+ //
+ CGFloat xoff = CTLineGetOffsetForStringIndex (dl, 0, NULL);
+ Assert (xoff >= 0, "unexpected CTLineOffset");
+ x -= xoff;
+
+ CGContextSetTextPosition (cgc,
+ wr.x + x,
+ wr.y + wr.height - y);
+ CGContextSetShouldAntialias (cgc, gc->gcv.antialias_p);
+
+ CTLineDraw (dl, cgc);
+ CFRelease (dl);
+ [astr release];
+
+ invalidate_drawable_cache (d);
+ return 0;
+}
+
+
+static int
+SetClipMask (Display *dpy, GC gc, Pixmap m)
+{
+ Assert (!!gc->gcv.clip_mask == !!gc->clip_mask, "GC clip mask mixup");
+
+ if (gc->gcv.clip_mask) {
+ XFreePixmap (dpy, gc->gcv.clip_mask);
+ CGImageRelease (gc->clip_mask);
+ }
+
+ gc->gcv.clip_mask = copy_pixmap (dpy, m);
+ if (gc->gcv.clip_mask)
+ gc->clip_mask =
+ CGBitmapContextCreateImage (gc->gcv.clip_mask->cgc);
+ else
+ gc->clip_mask = 0;
+
+ return 0;
+}
+
+static int
+SetClipOrigin (Display *dpy, GC gc, int x, int y)
+{
+ gc->gcv.clip_x_origin = x;
+ gc->gcv.clip_y_origin = y;
+ return 0;
+}
+
+
+const struct jwxyz_vtbl quartz_vtbl = {
+ root,
+ visual,
+ display_sources_data,
+
+ window_background,
+ draw_arc,
+ fill_rects,
+ gc_gcv,
+ gc_depth,
+ draw_string,
+
+ jwxyz_quartz_copy_area,
+
+ DrawPoints,
+ DrawSegments,
+ CreateGC,
+ FreeGC,
+ ClearWindow,
+ SetClipMask,
+ SetClipOrigin,
+ FillPolygon,
+ DrawLines,
+ PutImage,
+ GetSubImage
+};
+
+#endif // JWXYZ_QUARTZ -- entire file
diff --git a/jwxyz/jwxyzI.h b/jwxyz/jwxyzI.h
new file mode 100644
index 0000000..6a38d2e
--- /dev/null
+++ b/jwxyz/jwxyzI.h
@@ -0,0 +1,208 @@
+/* xscreensaver, Copyright (c) 1991-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+#ifndef __JWXYZ_I_H__
+#define __JWXYZ_I_H__
+
+#include <inttypes.h>
+#include <stddef.h>
+
+#include "jwxyz.h"
+
+#define Assert(C, ...) do { if (!(C)) jwxyz_abort (__VA_ARGS__); } while(0)
+
+#undef MAX
+#undef MIN
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#define MIN(a,b) ((a)<(b)?(a):(b))
+
+#define JWXYZ_FONT_FAMILY 0 /* i.e. -[NSFont familyName] via XLFD */
+#define JWXYZ_FONT_FACE 1 /* -[NSFont fontName] via native */
+#define JWXYZ_FONT_RANDOM 2
+
+#define JWXYZ_STYLE_BOLD 1
+#define JWXYZ_STYLE_ITALIC 2
+#define JWXYZ_STYLE_MONOSPACE 4
+
+#define JWXYZ_QUERY_COLOR(dpy, pixel, mult, rgba) \
+ { \
+ Visual *_V = DefaultVisualOfScreen (DefaultScreenOfDisplay (dpy)); \
+ (rgba)[0] = ((pixel) & _V->red_mask) * (mult) / _V->red_mask; \
+ (rgba)[1] = ((pixel) & _V->green_mask) * (mult) / _V->green_mask; \
+ (rgba)[2] = ((pixel) & _V->blue_mask) * (mult) / _V->blue_mask; \
+ (rgba)[3] = ((pixel) & _V->alpha_mask) * (mult) / _V->alpha_mask; \
+ }
+
+/* jwxyz.m, jwxyz-gl.c, jwxyz-image.c */
+extern void jwxyz_window_resized (Display *);
+
+/* jwxyz-cocoa.m, jwxyz-android.c */
+extern const XRectangle *jwxyz_frame (Drawable d); /* XGetGeometry sux. */
+extern unsigned int jwxyz_drawable_depth (Drawable d);
+
+/* 'scale' is pixels per point, with 72 points per inch. This matches the
+ meaning of -[UIView contentScaleFactor] on iOS.
+ */
+extern float jwxyz_scale (Window main_window);
+
+extern const char *jwxyz_default_font_family (int require);
+extern void *jwxyz_load_native_font (Window main_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);
+extern void jwxyz_release_native_font (Display *, void *native_font);
+
+/* Text metrics for aliased and antialiased text must match. */
+extern void jwxyz_render_text (Display *, void *native_font,
+ const char *str, size_t len, Bool utf8_p,
+ Bool antialias_p, XCharStruct *cs_ret,
+ char **pixmap_ret);
+extern void jwxyz_get_pos (Window w, XPoint *vpos, XPoint *p);
+#ifndef current_device_rotation
+extern double current_device_rotation (void);
+extern Bool ignore_rotation_p (Display *);
+#endif
+
+/* jwxyz-common.c */
+extern void jwxyz_validate_pixel (Display *dpy, unsigned long pixel,
+ unsigned int depth, Bool alpha_allowed_p);
+extern Bool jwxyz_dumb_drawing_mode(Display *dpy, Drawable d, GC gc,
+ int x, int y,
+ unsigned width, unsigned height);
+extern void jwxyz_blit (const void *src_data, ptrdiff_t src_pitch,
+ unsigned src_x, unsigned src_y,
+ void *dst_data, ptrdiff_t dst_pitch,
+ unsigned dst_x, unsigned dst_y,
+ unsigned width, unsigned height);
+extern void jwxyz_fill_rect (Display *, Drawable, GC,
+ int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long pixel);
+extern void jwxyz_gcv_defaults (Display *dpy, XGCValues *gcv, int depth);
+extern int jwxyz_draw_string (Display *dpy, Drawable d, GC gc, int x, int y,
+ const char *str, size_t len, int utf8_p);
+extern void *jwxyz_native_font (Font f);
+
+#define SEEK_XY(dst, dst_pitch, x, y) \
+ ((uint32_t *)((char *)dst + dst_pitch * y + x * 4))
+
+# ifdef JWXYZ_QUARTZ
+
+# include <CoreGraphics/CGGeometry.h>
+# include <CoreGraphics/CGContext.h>
+
+extern Display *jwxyz_quartz_make_display (Window w);
+extern void jwxyz_quartz_free_display (Display *);
+extern void jwxyz_flush_context (Display *);
+
+# define jwxyz_assert_display(dpy)
+# define jwxyz_assert_drawable(main_window, d)
+# define jwxyz_assert_gl()
+
+/* jwxyz-cocoa.m needs these from jwxyz.m for XCopyArea. */
+extern void invalidate_drawable_cache (Drawable d);
+extern void set_color (Display *dpy, CGContextRef cgc, unsigned long argb,
+ unsigned int depth, Bool alpha_allowed_p, Bool fill_p);
+extern void push_gc (Drawable d, GC gc);
+extern void push_color_gc (Display *dpy, Drawable d, GC gc,
+ unsigned long color,
+ Bool antialias_p, Bool fill_p);
+extern CGPoint map_point (Drawable d, int x, int y);
+extern void jwxyz_quartz_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);
+
+#define pop_gc(d,gc) CGContextRestoreGState (d->cgc)
+
+# endif /* JWXYZ_QUARTZ */
+
+# ifdef JWXYZ_GL
+
+# if defined(USE_IPHONE)
+# include <OpenGLES/ES1/gl.h>
+# elif defined(HAVE_COCOA)
+# include <OpenGL/gl.h>
+# elif defined(HAVE_ANDROID)
+# include <GLES/gl.h>
+# else
+# include <GL/gl.h>
+# endif
+
+/* utils/jwxyz-gl.c */
+extern Display *jwxyz_gl_make_display (Window w);
+extern void jwxyz_gl_free_display (Display *);
+extern void jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
+ Bool screen_p);
+extern void jwxyz_gl_flush (Display *dpy);
+extern void jwxyz_gl_set_gc (Display *dpy, GC gc);
+extern 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);
+
+/* Only works if both drawables share OpenGL objects. OpenGL context sharing
+ works on OS X, iOS, and some (but not all!) EGL implementations. This would
+ also work with FBOs and one context for everything. Surprisingly slow and
+ unreliable.
+ */
+extern void jwxyz_gl_copy_area_read_tex_image (Display *dpy,
+ unsigned src_height,
+ int src_x, int src_y,
+ unsigned int width,
+ unsigned int height,
+ int dst_x, int dst_y);
+extern void jwxyz_gl_copy_area_write_tex_image (Display *dpy, GC gc,
+ int src_x, int src_y,
+ unsigned int width,
+ unsigned int height,
+ int dst_x, int dst_y);
+
+extern void jwxyz_gl_draw_image (Display *dpy, GC gc,
+ GLenum gl_texture_target,
+ unsigned int tex_w, unsigned int tex_h,
+ int src_x, int src_y, int src_depth,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y, Bool flip_y);
+
+/* glReadPixels followed by glTexImage2D. This is terrible, so only use this
+ if nothing else works.
+ */
+extern void jwxyz_gl_copy_area_read_pixels (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);
+
+/* Platform-specific */
+extern void jwxyz_bind_drawable (Display *dpy, Window w, Drawable d);
+extern void jwxyz_assert_display (Display *);
+extern void jwxyz_assert_drawable (Window main_window, Drawable d);
+extern void jwxyz_assert_gl (void);
+
+# endif /* JWXYZ_GL */
+
+# ifdef JWXYZ_IMAGE
+
+extern Display *jwxyz_image_make_display (Window w,
+ const unsigned char *rgba_bytes);
+extern void jwxyz_image_free_display (Display *);
+
+extern ptrdiff_t jwxyz_image_pitch (Drawable d);
+extern void *jwxyz_image_data (Drawable d);
+
+# endif /* JWXYZ_IMAGE */
+
+#endif
diff --git a/jwxyz/jwzgles.c b/jwxyz/jwzgles.c
new file mode 100644
index 0000000..e0437e0
--- /dev/null
+++ b/jwxyz/jwzgles.c
@@ -0,0 +1,4331 @@
+/* xscreensaver, Copyright (c) 2012-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* A compatibility shim to allow OpenGL 1.3 source code to work in an
+ OpenGLES environment, where almost every OpenGL 1.3 function has
+ been "deprecated".
+
+ There are two major operations going on here:
+
+ - Converting calls to glBegin + glVertex3f + glEnd to glDrawArrays
+ - Implementing display lists.
+
+
+ From an API point of view, OpenGL 1.3 and earlier code looks like this:
+
+ glLightfv (GL_LIGHT0, GL_POSITION, ...);
+ glLightfv (GL_LIGHT0, GL_AMBIENT, ...);
+
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity ();
+ gluPerspective (...);
+
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity ();
+ gluLookAt (...);
+
+ glPushMatrix ();
+
+ glRotatef (...);
+
+ glColor3f (...);
+
+ glBegin (GL_TRIANGLES);
+ glNormal3f (...);
+ glVertex3f (...);
+ glVertex3f (...);
+ glVertex3f (...);
+ glEnd ();
+
+ glPopMatrix ();
+
+ glFinish ();
+
+
+ OpenGLES broke that model by eliminating glBegin(). Instead of
+ iterating a sequence of vertexes, you need to pack your points into
+ an array first, e.g.:
+
+ GLfloat coords[] = {
+ 0, 0, 0,
+ 0, 1, 0,
+ ...
+ };
+
+ glDrawArrays (GL_TRIANGLES, 0, 3);
+
+ The projection model (glRotatef, etc.) works the same, but glColor()
+ is missing. You're expected to encode that into your arrays.
+
+ Also, OpenGLES doesn't support display lists at all.
+
+
+ So this code shadows all of the functions that are allowed within
+ glBegin, builds up an array, and calls glDrawArrays at the end.
+
+ Likewise, it shadows all of the functions that are allowed within
+ glNewList and records those calls for later playback.
+
+
+ This code only handles OpenGLES 1.1, not 1.0 or 2.x.
+
+ OpenGLES 2.0 broke things further by eliminating the whole OpenGL
+ lighting model. Instead of specifying the positions and properties
+ of your lights using the glLight* API, now you are expected to
+ implement it all yourself by downloading C-like code into the GPU
+ directly. This is more flexible, in that if you wanted a completely
+ different lighting model than what OpenGL provides, you could do
+ that, but it leaves you needing to download boilerplate to reproduce
+ what used to be built in.
+
+
+ Incidentally, the OpenGL numbering scheme goes something like this:
+
+ OpenGL 1.0 1992
+ OpenGL 1.1 1997 (improved texture support)
+ OpenGL 1.2 1998 (nothing interesting)
+ OpenGL 1.3 2001 (multisampling, cubemaps)
+ OpenGL 1.4 2002 (added auto-mipmapping)
+ OpenGLES 1.0 2003 (deprecated 80% of the language; fork of OpenGL 1.3)
+ OpenGL 1.5 2003 (added VBOs)
+ OpenGLES 1.1 2004 (fork of OpenGL 1.5)
+ OpenGL 2.0 2004 (a political quagmire)
+ OpenGLES 2.0 2007 (deprecated 95% of the language; fork of OpenGL 2.0)
+ OpenGL 3.0 2008 (added FBOs, VAOs, deprecated 60% of the language)
+
+
+ Some things that are missing:
+
+ - glTexGeni, meaning no spherical environment-mapping textures.
+
+ - gluNewTess, meaning no tesselation of complex objects.
+
+ - glMap2f mesh evaluators, meaning no Utah Teapot.
+
+ - glPolygonMode with GL_LINE or GL_POINT, meaning no wireframe modes
+ that do hidden-surface removal.
+
+ - glSelectBuffer, meaning no mouse-hit detection on rendered objects.
+
+ - gluNewQuadric, gluCylinder, etc: rewrite your code to use tube.c, etc.
+
+ - Putting verts in a display list without a wrapping glBegin.
+ I didn't realize that even worked! Lockward used to do that,
+ before I fixed it to not.
+
+ - Not every function is implemented; just the ones that I needed for
+ xscreensaver. However, the trivial ones are trivial to enable
+ as they come up. Harder ones will be harder.
+
+ As a result of that, these savers look wrong:
+
+ atlantis Uses EYE_PLANE.
+ blocktube Uses SPHERE_MAP.
+ dnalogo Uses GLUtesselator.
+ extrusion Uses all kinds of GLUT crap.
+ flyingtoasters Uses SPHERE_MAP.
+ winduprobot Uses SPHERE_MAP.
+ jigglypuff Uses SPHERE_MAP (in chrome mode), GL_LINE (in wireframe)
+ jigsaw Uses GLUtesselator.
+ pinion Uses glSelectBuffer and gluPickMatrix for mouse-clicks.
+ pipes Uses glMap2f for the Utah Teapot.
+ polyhedra Uses GLUtesselator (concave objects); also Utah Teapot.
+ skytentacles Uses GL_LINE in -cel mode.
+ timetunnel Uses GL_CONSTANT_ALPHA and all kinds of other stuff.
+*/
+
+
+#undef DEBUG
+
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif /* HAVE_CONFIG_H */
+
+#ifdef HAVE_JWZGLES /* whole file */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <math.h>
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif /* HAVE_UNISTD_H */
+
+#if defined(USE_IPHONE)
+# include <OpenGLES/ES1/gl.h>
+# include <OpenGLES/ES1/glext.h>
+#elif defined(HAVE_COCOA)
+# include <OpenGL/gl.h>
+# include <OpenGL/glu.h>
+#elif defined(HAVE_ANDROID)
+# include <GLES/gl.h>
+# include <android/log.h>
+#else /* real X11 */
+# ifndef GL_GLEXT_PROTOTYPES
+# define GL_GLEXT_PROTOTYPES /* for glBindBuffer */
+# endif
+# include <GL/glx.h>
+# include <GL/glu.h>
+#endif
+
+#include "jwzglesI.h"
+
+#include "pow2.h"
+
+#define STRINGIFY(X) #X
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef Assert
+
+#ifdef HAVE_COCOA
+ extern void jwxyz_abort (const char *fmt, ...) __dead2;
+# define Assert(C,S) do { if (!(C)) { jwxyz_abort ("%s",S); }} while(0)
+#elif defined HAVE_ANDROID
+# define Assert(C,S) do { \
+ if (!(C)) { \
+ __android_log_print (ANDROID_LOG_ERROR, "xscreensaver", "jwzgles: %s\n", S); \
+ abort(); \
+ }} while(0)
+#else
+# define Assert(C,S) do { \
+ if (!(C)) { \
+ fprintf (stderr, "jwzgles: %s\n", S); \
+ abort(); \
+ }} while(0)
+#endif
+
+
+typedef struct { GLfloat x, y, z; } XYZ;
+typedef struct { GLfloat x, y, z, w; } XYZW;
+typedef struct { GLfloat s, t, r, q; } STRQ;
+typedef struct { GLfloat r, g, b, a; } RGBA;
+
+
+/* Used to record all calls to glVertex3f, glNormal3f, etc.
+ while inside glBegin / glEnd so that we can convert that
+ to a single call to glDrawArrays.
+ */
+typedef struct {
+ int mode;
+ int count, size; /* size of each array */
+
+ XYZW *verts; /* Arrays being built */
+ XYZ *norms;
+ STRQ *tex;
+ RGBA *color;
+
+ int ncount; /* How many normals, tex coords and colors were */
+ int tcount; /* used. We optimize based on "0, 1, or many". */
+ int ccount;
+ int materialistic; /* Whether glMaterial was called inside glBegin */
+
+ XYZ cnorm; /* Prevailing normal/texture/color while building */
+ STRQ ctex;
+ RGBA ccolor;
+
+} vert_set;
+
+
+typedef void (*list_fn_cb) (void);
+
+
+/* We need this nonsense because you can't cast a double to a void*
+ or vice versa. They tend to be passed in different registers,
+ and you need to know about that because it's still 1972 here.
+ */
+typedef union {
+ const void *v; GLfloat f; GLuint i; GLshort s; GLdouble d;
+} void_int;
+
+typedef struct { /* saved args for glDrawArrays */
+ int binding, size, type, stride, bytes;
+ void *data;
+} draw_array;
+
+typedef enum { /* shorthand describing arglist signature */
+ PROTO_VOID, /* no args */
+ PROTO_I, /* 1 int arg */
+ PROTO_F, /* 1 float arg */
+ PROTO_II, /* int, int */
+ PROTO_FF, /* float, float */
+ PROTO_IF, /* int, float */
+ PROTO_III, /* int, int, int */
+ PROTO_FFF, /* float, float, float */
+ PROTO_IIF, /* int, int, float */
+ PROTO_IIII, /* int, int, int, int */
+ PROTO_FFFF, /* float, float, float, float */
+ PROTO_IIV, /* int, int[4] */
+ PROTO_IFV, /* int, float[4] */
+ PROTO_IIIV, /* int, int, int[4] */
+ PROTO_IIFV, /* int, int, float[4] */
+ PROTO_FV16, /* float[16] */
+ PROTO_ARRAYS /* glDrawArrays */
+} fn_proto;
+
+typedef struct { /* A single element of a display list */
+ const char *name;
+ list_fn_cb fn; /* saved function pointer */
+ fn_proto proto; /* arglist prototype */
+ draw_array *arrays; /* args for glDrawArrays */
+ void_int argv[16]; /* args for everything else */
+} list_fn;
+
+
+typedef struct { /* a display list: saved activity within glNewList */
+ int id;
+ int size, count;
+ list_fn *fns;
+
+ /* Named buffer that should be freed when this display list is deleted. */
+ GLuint buffer;
+
+} list;
+
+
+typedef struct { /* All display lists */
+ list *lists;
+ int count, size;
+} list_set;
+
+
+#define ISENABLED_TEXTURE_2D (1<<0)
+#define ISENABLED_TEXTURE_GEN_S (1<<1)
+#define ISENABLED_TEXTURE_GEN_T (1<<2)
+#define ISENABLED_TEXTURE_GEN_R (1<<3)
+#define ISENABLED_TEXTURE_GEN_Q (1<<4)
+#define ISENABLED_LIGHTING (1<<5)
+#define ISENABLED_BLEND (1<<6)
+#define ISENABLED_DEPTH_TEST (1<<7)
+#define ISENABLED_CULL_FACE (1<<8)
+#define ISENABLED_NORMALIZE (1<<9)
+#define ISENABLED_FOG (1<<10)
+#define ISENABLED_COLMAT (1<<11)
+#define ISENABLED_VERT_ARRAY (1<<12)
+#define ISENABLED_NORM_ARRAY (1<<13)
+#define ISENABLED_TEX_ARRAY (1<<14)
+#define ISENABLED_COLOR_ARRAY (1<<15)
+#define ISENABLED_ALPHA_TEST (1<<16)
+
+
+typedef struct {
+ GLuint mode;
+ GLfloat obj[4], eye[4];
+} texgen_state;
+
+
+struct jwzgles_state { /* global state */
+
+ vert_set set; /* set being built */
+
+ int compiling_list; /* list id if inside glNewList; 0 means immediate */
+ int replaying_list; /* depth of call stack to glCallList */
+ int compiling_verts; /* inside glBegin */
+
+ list_set lists; /* saved lists */
+
+ unsigned long enabled; /* enabled flags, immediate mode */
+ unsigned long list_enabled; /* and for the list-in-progress */
+
+ texgen_state s, t, r, q;
+
+ /* Implementing glPushClientAttrib? Don't forget about these! */
+ draw_array varray, narray, carray, tarray;
+
+};
+
+
+static jwzgles_state *state = 0;
+
+
+#ifdef DEBUG
+
+static void Log(const char *fmt, ...)
+{
+ va_list args;
+ va_start (args, fmt);
+#ifdef HAVE_ANDROID
+ /* setprop log.redirect-stdio true is another possibility, but that
+ apparently only works on rooted devices.
+ */
+ __android_log_vprint(ANDROID_LOG_DEBUG, "xscreensaver", fmt, args);
+# else
+ vfprintf(stderr, fmt, args);
+# endif
+ va_end (args);
+}
+
+# define LOG(A) Log("jwzgles: " A "\n")
+# define LOG1(A,B) Log("jwzgles: " A "\n",B)
+# define LOG2(A,B,C) Log("jwzgles: " A "\n",B,C)
+# define LOG3(A,B,C,D) Log("jwzgles: " A "\n",B,C,D)
+# define LOG4(A,B,C,D,E) Log("jwzgles: " A "\n",B,C,D,E)
+# define LOG5(A,B,C,D,E,F) Log("jwzgles: " A "\n",B,C,D,E,F)
+# define LOG6(A,B,C,D,E,F,G) Log("jwzgles: " A "\n",B,C,D,E,F,G)
+# define LOG7(A,B,C,D,E,F,G,H) Log("jwzgles: " A "\n",B,C,D,E,F,G,H)
+# define LOG8(A,B,C,D,E,F,G,H,I)\
+ Log("jwzgles: "A "\n",B,C,D,E,F,G,H,I)
+# define LOG9(A,B,C,D,E,F,G,H,I,J)\
+ Log("jwzgles: "A "\n",B,C,D,E,F,G,H,I,J)
+# define LOG10(A,B,C,D,E,F,G,H,I,J,K)\
+ Log("jwzgles: "A "\n",B,C,D,E,F,G,H,I,J,K)
+# define LOG17(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R)\
+ Log("jwzgles: "A "\n",B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R)
+# define CHECK(S) jwzgles_check_gl_error(S)
+#else
+# define LOG(A) /* */
+# define LOG1(A,B) /* */
+# define LOG2(A,B,C) /* */
+# define LOG3(A,B,C,D) /* */
+# define LOG4(A,B,C,D,E) /* */
+# define LOG5(A,B,C,D,E,F) /* */
+# define LOG6(A,B,C,D,E,F,G) /* */
+# define LOG7(A,B,C,D,E,F,G,H) /* */
+# define LOG8(A,B,C,D,E,F,G,H,I) /* */
+# define LOG9(A,B,C,D,E,F,G,H,I,J) /* */
+# define LOG10(A,B,C,D,E,F,G,H,I,J,K) /* */
+# define LOG17(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R) /* */
+# define CHECK(S) /* */
+#endif
+
+#ifdef DEBUG
+static const char *
+mode_desc (int mode) /* for debugging messages */
+{
+ switch (mode) {
+# define SS(X) case GL_##X: return STRINGIFY(X);
+ SS(ALPHA)
+ SS(ALPHA_TEST)
+ SS(AMBIENT)
+ SS(AMBIENT_AND_DIFFUSE)
+ SS(ARRAY_BUFFER)
+ SS(AUTO_NORMAL)
+ SS(BACK)
+ SS(BLEND)
+ SS(BLEND_DST)
+ SS(BLEND_SRC)
+ SS(BLEND_SRC_ALPHA)
+ SS(BYTE)
+ SS(C3F_V3F)
+ SS(C4F_N3F_V3F)
+ SS(C4UB_V2F)
+ SS(C4UB_V3F)
+ SS(CCW)
+ SS(CLAMP)
+ SS(COLOR_ARRAY)
+ SS(COLOR_ARRAY_BUFFER_BINDING);
+ SS(COLOR_MATERIAL)
+ SS(COLOR_MATERIAL_FACE)
+ SS(COLOR_MATERIAL_PARAMETER)
+ SS(COMPILE)
+ SS(CULL_FACE)
+ SS(CW)
+ SS(DECAL)
+ SS(DEPTH_BUFFER_BIT)
+ SS(DEPTH_TEST)
+ SS(DIFFUSE)
+ SS(DOUBLEBUFFER)
+ SS(DST_ALPHA)
+ SS(DST_COLOR)
+ SS(DYNAMIC_DRAW)
+ SS(ELEMENT_ARRAY_BUFFER)
+ SS(EYE_LINEAR)
+ SS(EYE_PLANE)
+ SS(FEEDBACK)
+ SS(FILL)
+ SS(FLAT)
+ SS(FLOAT)
+ SS(FOG)
+ SS(FRONT)
+ SS(FRONT_AND_BACK)
+ SS(GREATER)
+ SS(INTENSITY)
+ SS(INVALID_ENUM)
+ SS(INVALID_OPERATION)
+ SS(INVALID_VALUE)
+ SS(LESS)
+ SS(LIGHT0)
+ SS(LIGHT1)
+ SS(LIGHT2)
+ SS(LIGHT3)
+ SS(LIGHTING)
+ SS(LIGHT_MODEL_AMBIENT)
+ SS(LIGHT_MODEL_COLOR_CONTROL)
+ SS(LIGHT_MODEL_LOCAL_VIEWER)
+ SS(LIGHT_MODEL_TWO_SIDE)
+ SS(LINE)
+ SS(LINEAR)
+ SS(LINEAR_MIPMAP_LINEAR)
+ SS(LINEAR_MIPMAP_NEAREST)
+ SS(LINES)
+ SS(LINE_LOOP)
+ SS(LINE_STRIP)
+ SS(LUMINANCE)
+ SS(LUMINANCE_ALPHA)
+ SS(MATRIX_MODE)
+ SS(MODELVIEW)
+ SS(MODULATE)
+ SS(N3F_V3F)
+ SS(NEAREST)
+ SS(NEAREST_MIPMAP_LINEAR)
+ SS(NEAREST_MIPMAP_NEAREST)
+ SS(NORMALIZE)
+ SS(NORMAL_ARRAY)
+ SS(NORMAL_ARRAY_BUFFER_BINDING);
+ SS(OBJECT_LINEAR)
+ SS(OBJECT_PLANE)
+ SS(ONE_MINUS_DST_ALPHA)
+ SS(ONE_MINUS_DST_COLOR)
+ SS(ONE_MINUS_SRC_ALPHA)
+ SS(ONE_MINUS_SRC_COLOR)
+ SS(OUT_OF_MEMORY)
+ SS(PACK_ALIGNMENT)
+ SS(POINTS)
+ SS(POLYGON)
+ SS(POLYGON_OFFSET_FILL)
+ SS(POLYGON_SMOOTH)
+ SS(POLYGON_STIPPLE)
+ SS(POSITION)
+ SS(PROJECTION)
+ SS(Q)
+ SS(QUADS)
+ SS(QUAD_STRIP)
+ SS(R)
+ SS(RENDER)
+ SS(REPEAT)
+ SS(RGB)
+ SS(RGBA)
+ SS(RGBA_MODE)
+ SS(S)
+ SS(SELECT)
+ SS(SEPARATE_SPECULAR_COLOR)
+ SS(SHADE_MODEL)
+ SS(SHININESS)
+ SS(SHORT)
+ SS(SINGLE_COLOR)
+ SS(SMOOTH)
+ SS(SPECULAR)
+ SS(SPHERE_MAP)
+ SS(SRC_ALPHA)
+ SS(SRC_ALPHA_SATURATE)
+ SS(SRC_COLOR)
+ SS(STACK_OVERFLOW)
+ SS(STACK_UNDERFLOW)
+ SS(STATIC_DRAW)
+ SS(STENCIL_BUFFER_BIT)
+ SS(T)
+ SS(T2F_C3F_V3F)
+ SS(T2F_C4F_N3F_V3F)
+ SS(T2F_C4UB_V3F)
+ SS(T2F_N3F_V3F)
+ SS(T2F_V3F)
+ SS(T4F_C4F_N3F_V4F)
+ SS(T4F_V4F)
+ SS(TEXTURE)
+ SS(TEXTURE_1D)
+ SS(TEXTURE_2D)
+ SS(TEXTURE_ALPHA_SIZE)
+ SS(TEXTURE_BINDING_2D)
+ SS(TEXTURE_BLUE_SIZE)
+ SS(TEXTURE_BORDER)
+ SS(TEXTURE_BORDER_COLOR)
+ SS(TEXTURE_COMPONENTS)
+ SS(TEXTURE_COORD_ARRAY)
+ SS(TEXTURE_COORD_ARRAY_BUFFER_BINDING);
+ SS(TEXTURE_ENV)
+ SS(TEXTURE_ENV_COLOR)
+ SS(TEXTURE_ENV_MODE)
+ SS(TEXTURE_GEN_MODE)
+ SS(TEXTURE_GEN_Q)
+ SS(TEXTURE_GEN_R)
+ SS(TEXTURE_GEN_S)
+ SS(TEXTURE_GEN_T)
+ SS(TEXTURE_GREEN_SIZE)
+ SS(TEXTURE_HEIGHT)
+ SS(TEXTURE_INTENSITY_SIZE)
+ SS(TEXTURE_LUMINANCE_SIZE)
+ SS(TEXTURE_MAG_FILTER)
+ SS(TEXTURE_MIN_FILTER)
+ SS(TEXTURE_RED_SIZE)
+ SS(TEXTURE_WRAP_S)
+ SS(TEXTURE_WRAP_T)
+ SS(TRIANGLES)
+ SS(TRIANGLE_FAN)
+ SS(TRIANGLE_STRIP)
+ SS(UNPACK_ALIGNMENT)
+ SS(UNPACK_ROW_LENGTH)
+ SS(UNSIGNED_BYTE)
+ SS(UNSIGNED_INT_8_8_8_8_REV)
+ SS(UNSIGNED_SHORT)
+ SS(V2F)
+ SS(V3F)
+ SS(VERTEX_ARRAY)
+ SS(VERTEX_ARRAY_BUFFER_BINDING);
+/*SS(COLOR_BUFFER_BIT) -- same value as GL_LIGHT0 */
+# undef SS
+ case (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT):
+ return "DEPTH_BUFFER_BIT | COLOR_BUFFER_BIT";
+/* Oops, same as INVALID_ENUM.
+ case (GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT):
+ return "DEPTH_BUFFER_BIT | STENCIL_BUFFER_BIT";
+*/
+ case (GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT):
+ return "COLOR_BUFFER_BIT | STENCIL_BUFFER_BIT";
+ case (GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT):
+ return "DEPTH_BUFFER_BIT | COLOR_BUFFER_BIT | STENCIL_BUFFER_BIT";
+ default:
+ {
+ static char buf[255];
+ sprintf (buf, "0x%04X", mode);
+ return buf;
+ }
+ }
+}
+
+static void
+jwzgles_check_gl_error (const char *s)
+{
+ GLenum i = glGetError();
+ if (i == GL_NO_ERROR) return;
+ fprintf (stderr, "jwzgles: GL ERROR: %s: %s\n", s, mode_desc(i));
+}
+
+#endif /* DEBUG */
+
+
+static void
+make_room (const char *name, void **array, int span, int *count, int *size)
+{
+ if (*count + 1 >= *size)
+ {
+ int new_size = (*count + 20) * 1.2; /* mildly exponential */
+ *array = realloc (*array, new_size * span);
+ Assert (*array, "out of memory");
+ /* LOG3("%s: grew %d -> %d", name, *size, new_size); */
+ *size = new_size;
+ }
+}
+
+
+void
+jwzgles_free_state (void)
+{
+ /* Tricky: jwzgles_make_state doesn't require an active GLES context, but
+ jwzgles_free_state does.
+ */
+
+ LOG1("jwzgles_free_state %p", state);
+
+ if (state->lists.lists)
+ {
+ state->compiling_list = 0;
+ if (state->lists.count)
+ jwzgles_glDeleteLists (1, state->lists.count);
+ free (state->lists.lists);
+ }
+
+ if (state->set.verts) free (state->set.verts);
+ if (state->set.norms) free (state->set.norms);
+ if (state->set.tex) free (state->set.tex);
+ if (state->set.color) free (state->set.color);
+
+ free (state);
+ state = NULL;
+}
+
+
+jwzgles_state *
+jwzgles_make_state (void)
+{
+ jwzgles_state *s = (jwzgles_state *) calloc (1, sizeof (*s));
+
+ LOG1("jwzgles_make_state %p", s);
+
+ s->s.mode = s->t.mode = s->r.mode = s->q.mode = GL_EYE_LINEAR;
+ s->s.obj[0] = s->s.eye[0] = 1; /* s = 1 0 0 0 */
+ s->t.obj[1] = s->t.eye[1] = 1; /* t = 0 1 0 0 */
+
+ return s;
+}
+
+
+void
+jwzgles_make_current (jwzgles_state *s)
+{
+ LOG1("jwzgles_make_current %p", s);
+ state = s;
+}
+
+
+int
+jwzgles_glGenLists (int n)
+{
+ int i;
+ int ret = 0;
+
+ Assert (!state->compiling_verts, "glGenLists not allowed inside glBegin");
+
+ /* Ensure space in state->lists, clear the one at the end, and tick counter
+ Note that lists are never really deleted, and we can never re-use elements
+ of this array. glDeleteLists zeroes out the contents of the list, but
+ the list ID is still valid for use with glNewList forever.
+ #### So maybe this should be a linked list instead of an array.
+ */
+ for (i = 0; i < n; i++)
+ {
+ list *L;
+ int id = 0;
+ make_room ("glGenLists",
+ (void **) &state->lists.lists,
+ sizeof (*state->lists.lists),
+ &state->lists.count, &state->lists.size);
+ state->lists.count++;
+ id = state->lists.count;
+ L = &state->lists.lists[id-1];
+
+ memset (L, 0, sizeof (*L));
+ L->id = id;
+ if (ret == 0) ret = id;
+ LOG1("glGenLists -> %d", L->id);
+ }
+
+ /* Return the ID of the first list allocated */
+
+ return ret;
+}
+
+
+void
+jwzgles_glNewList (int id, int mode)
+{
+ list *L;
+ Assert (id > 0 && id <= state->lists.count, "glNewList: bogus ID");
+ Assert (mode == GL_COMPILE, "glNewList: bad mode");
+ Assert (!state->compiling_verts, "glNewList not allowed inside glBegin");
+ Assert (!state->compiling_list, "nested glNewList");
+ Assert (state->set.count == 0, "missing glEnd");
+
+ L = &state->lists.lists[id-1];
+ Assert (L->id == id, "glNewList corrupted");
+
+ if (L->count != 0) jwzgles_glDeleteLists (L->id, 1); /* Overwriting */
+ Assert (L->count == 0, "glNewList corrupted");
+
+ state->compiling_list = id;
+
+ state->list_enabled = state->enabled;
+
+ LOG1("glNewList -> %d", id);
+}
+
+
+static void save_arrays (list_fn *, int);
+static void restore_arrays (list_fn *, int);
+static void copy_array_data (draw_array *, int, const char *);
+static void optimize_arrays (void);
+static void generate_texture_coords (GLuint, GLuint);
+
+
+void
+jwzgles_glEndList (void)
+{
+ Assert (state->compiling_list, "extra glEndList");
+ Assert (state->set.count == 0, "missing glEnd");
+ Assert (!state->compiling_verts, "glEndList not allowed inside glBegin");
+ LOG1("glEndList %d", state->compiling_list);
+ optimize_arrays();
+ state->compiling_list = 0;
+ state->list_enabled = state->enabled;
+}
+
+
+static void
+list_push (const char * const name,
+ list_fn_cb fn, fn_proto proto, void_int *av)
+{
+ list *L;
+ list_fn *F;
+ int i;
+
+ Assert (state->compiling_list > 0, "not inside glNewList");
+ Assert (state->compiling_list <= state->lists.count, "glNewList corrupted");
+
+ L = &state->lists.lists[state->compiling_list-1];
+ Assert (L, "glNewList: no list");
+
+ make_room ("glNewLists",
+ (void **) &L->fns, sizeof (*L->fns),
+ &L->count, &L->size);
+ memset (&L->fns[L->count], 0, sizeof (*L->fns));
+ F = L->fns + L->count;
+
+ F->name = name;
+ F->fn = fn;
+ F->proto = proto;
+ if (proto != PROTO_VOID)
+ for (i = 0; i < countof(F->argv); i++)
+ F->argv[i] = av[i];
+
+# ifdef DEBUG
+ switch (proto) {
+ case PROTO_VOID:
+ LOG1 (" push %-12s", name);
+ break;
+ case PROTO_I:
+ if (fn == (list_fn_cb) &jwzgles_glBegin ||
+ fn == (list_fn_cb) &jwzgles_glFrontFace ||
+ fn == (list_fn_cb) &jwzgles_glEnable ||
+ fn == (list_fn_cb) &jwzgles_glDisable ||
+ fn == (list_fn_cb) &jwzgles_glEnableClientState ||
+ fn == (list_fn_cb) &jwzgles_glDisableClientState ||
+ fn == (list_fn_cb) &jwzgles_glShadeModel ||
+ fn == (list_fn_cb) &jwzgles_glMatrixMode)
+ LOG2 (" push %-12s %s", name, mode_desc (av[0].i));
+ else
+ LOG2 (" push %-12s %d", name, av[0].i);
+ break;
+ case PROTO_F:
+ LOG2 (" push %-12s %7.3f", name, av[0].f);
+ break;
+ case PROTO_II:
+ if (fn == (list_fn_cb) &jwzgles_glBindTexture ||
+ fn == (list_fn_cb) &jwzgles_glBindBuffer)
+ LOG3 (" push %-12s %s %d", name, mode_desc (av[0].i), av[1].i);
+ else
+ LOG3 (" push %-12s %d %d", name, av[0].i, av[1].i);
+ break;
+ case PROTO_FF:
+ LOG3 (" push %-12s %7.3f %7.3f", name, av[0].f, av[1].f);
+ break;
+ case PROTO_IF:
+ LOG3 (" push %-12s %s %7.3f", name, mode_desc (av[0].i), av[1].f);
+ break;
+ case PROTO_III:
+ case PROTO_ARRAYS:
+ if (fn == (list_fn_cb) &jwzgles_glDrawArrays ||
+ fn == (list_fn_cb) &jwzgles_glTexParameteri)
+ LOG4 (" push %-12s %s %d %d", name, mode_desc (av[0].i),
+ av[1].i, av[2].i);
+ else
+ LOG4 (" push %-12s %d %d %d", name, av[0].i, av[1].i, av[2].i);
+ break;
+ case PROTO_FFF:
+ LOG4 (" push %-12s %7.3f %7.3f %7.3f", name, av[0].f, av[1].f, av[2].f);
+ break;
+ case PROTO_IIF:
+ LOG4 (" push %-12s %s %s %7.3f", name,
+ mode_desc(av[0].i), mode_desc(av[1].i), av[2].f);
+ break;
+ case PROTO_IIII:
+ LOG5 (" push %-12s %d %d %d %d", name,
+ av[0].i, av[1].i, av[2].i, av[3].i);
+ break;
+ case PROTO_FFFF:
+ LOG5 (" push %-12s %7.3f %7.3f %7.3f %7.3f", name,
+ av[0].f, av[1].f, av[2].f, av[3].f);
+ break;
+ case PROTO_IFV:
+ LOG6 (" push %-12s %s %3.1f %3.1f %3.1f %3.1f", name, mode_desc (av[0].i),
+ av[1].f, av[2].f, av[3].f, av[4].f);
+ break;
+ case PROTO_IIV:
+ LOG6 (" push %-12s %s %d %d %d %d", name, mode_desc (av[0].i),
+ av[1].i, av[2].i, av[3].i, av[4].i);
+ break;
+ case PROTO_IIFV:
+ LOG7 (" push %-12s %s %-8s %3.1f %3.1f %3.1f %3.1f", name,
+ mode_desc (av[0].i), mode_desc (av[1].i),
+ av[2].f, av[3].f, av[4].f, av[5].f);
+ break;
+ case PROTO_IIIV:
+ LOG7 (" push %-12s %s %-8s %3d %3d %3d %3d", name,
+ mode_desc (av[0].i), mode_desc (av[1].i),
+ av[2].i, av[3].i, av[4].i, av[5].i);
+ break;
+ case PROTO_FV16:
+ LOG17 (" push %-12s ["
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f ]",
+ name,
+ av[0].f, av[1].f, av[2].f, av[3].f,
+ av[4].f, av[5].f, av[6].f, av[7].f,
+ av[8].f, av[9].f, av[10].f, av[11].f,
+ av[12].f, av[13].f, av[14].f, av[15].f);
+ break;
+ default:
+ Assert (0, "bogus prototype");
+ break;
+ }
+# endif /* DEBUG */
+
+ if (proto == PROTO_ARRAYS) /* glDrawArrays */
+ save_arrays (F, av[1].i + av[2].i);
+
+ L->count++;
+}
+
+
+void
+jwzgles_glBegin (int mode)
+{
+ Assert (!state->compiling_verts, "nested glBegin");
+ state->compiling_verts++;
+
+ /* Only these commands are allowed inside glBegin:
+
+ glVertex -- not allowed outside
+ glColor
+ glSecondaryColor
+ glIndex
+ glNormal
+ glFogCoord
+ glTexCoord
+ glMultiTexCoord
+ glVertexAttrib
+ glEvalCoord
+ glEvalPoint
+ glArrayElement -- not allowed outside
+ glMaterial
+ glEdgeFlag
+ glCallList
+ glCallLists
+ */
+
+ if (!state->replaying_list)
+ LOG2 ("%sglBegin %s",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ mode_desc (mode));
+
+ Assert (state->set.count == 0, "glBegin corrupted");
+ state->set.mode = mode;
+ state->set.count = 0;
+ state->set.ncount = 0;
+ state->set.tcount = 0;
+ state->set.ccount = 0;
+}
+
+
+void
+jwzgles_glDeleteLists (int id0, int range)
+{
+ Assert (!state->compiling_verts, "glDeleteLists not allowed inside glBegin");
+
+ if (state->compiling_list)
+ {
+ void_int vv[2];
+ vv[0].i = id0;
+ vv[1].i = range;
+ list_push ("glDeleteLists", (list_fn_cb) &jwzgles_glDeleteLists,
+ PROTO_II, vv);
+ }
+ else
+ {
+ int id;
+
+ if (!state->replaying_list)
+ LOG2 ("glDeleteLists %d %d", id0, range);
+
+ for (id = id0 + range - 1; id >= id0; id--)
+ {
+ int i;
+ list *L;
+ if (id == 0) continue; /* People do this stupid thing */
+ if (id > state->lists.count) break; /* this too */
+ Assert (id > 0 && id <= state->lists.count,
+ "glDeleteLists: bogus ID");
+ L = &state->lists.lists[id-1];
+ Assert (L->id == id, "glDeleteLists corrupted");
+
+ for (i = 0; i < L->count; i++)
+ {
+ list_fn *lf = &L->fns[i];
+ if (lf->arrays)
+ {
+ int j;
+ for (j = 0; j < 4; j++)
+ /* If there's a binding, 'data' is an index, not a ptr. */
+ if (!lf->arrays[j].binding &&
+ lf->arrays[j].data)
+ free (lf->arrays[j].data);
+ free (lf->arrays);
+ }
+ }
+ if (L->fns)
+ free (L->fns);
+ if (L->buffer)
+ glDeleteBuffers (1, &L->buffer);
+
+ memset (L, 0, sizeof (*L));
+ L->id = id;
+ }
+ }
+}
+
+
+extern GLboolean
+jwzgles_glIsList (GLuint id)
+{
+ return (id > 0 && id < state->lists.count);
+}
+
+
+
+void
+jwzgles_glNormal3fv (const GLfloat *v)
+{
+ if (state->compiling_list && !state->compiling_verts)
+ {
+ void_int vv[3];
+ vv[0].f = v[0];
+ vv[1].f = v[1];
+ vv[2].f = v[2];
+ list_push ("glNormal3f", (list_fn_cb) &jwzgles_glNormal3f,
+ PROTO_FFF, vv);
+ }
+ else
+ {
+ if (!state->replaying_list)
+ LOG5 ("%s%sglNormal3f %7.3f %7.3f %7.3f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ (state->compiling_verts ? " rec " : ""),
+ v[0], v[1], v[2]);
+
+ if (state->compiling_verts) /* inside glBegin */
+ {
+ state->set.cnorm.x = v[0];
+ state->set.cnorm.y = v[1];
+ state->set.cnorm.z = v[2];
+ state->set.ncount++;
+ if (state->set.count > 0 && state->set.ncount == 1) /* not first! */
+ state->set.ncount++;
+ }
+ else /* outside glBegin */
+ {
+ glNormal3f (v[0], v[1], v[2]);
+ CHECK("glNormal3f");
+ }
+ }
+}
+
+
+void
+jwzgles_glNormal3f (GLfloat x, GLfloat y, GLfloat z)
+{
+ GLfloat v[3];
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+ jwzgles_glNormal3fv (v);
+}
+
+
+void
+jwzgles_glTexCoord4fv (const GLfloat *v)
+{
+ if (state->compiling_list && !state->compiling_verts)
+ {
+ void_int vv[4];
+ vv[0].f = v[0];
+ vv[1].f = v[1];
+ vv[2].f = v[2];
+ vv[3].f = v[3];
+ list_push ("glTexCoord4f", (list_fn_cb) &jwzgles_glTexCoord4f,
+ PROTO_FFFF, vv);
+ }
+ else
+ {
+ if (!state->replaying_list)
+ LOG6 ("%s%sglTexCoord4f %7.3f %7.3f %7.3f %7.3f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ (state->compiling_verts ? " rec " : ""),
+ v[0], v[1], v[2], v[3]);
+
+ Assert (state->compiling_verts, "glTexCoord4fv outside glBegin");
+
+ if (state->compiling_verts) /* inside glBegin */
+ {
+ state->set.ctex.s = v[0];
+ state->set.ctex.t = v[1];
+ state->set.ctex.r = v[2];
+ state->set.ctex.q = v[3];
+ state->set.tcount++;
+ if (state->set.count > 0 && state->set.tcount == 1) /* not first! */
+ state->set.tcount++;
+ }
+ }
+}
+
+
+void
+jwzgles_glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q)
+{
+ GLfloat v[4];
+ v[0] = s;
+ v[1] = t;
+ v[2] = r;
+ v[3] = q;
+ jwzgles_glTexCoord4fv (v);
+}
+
+
+void
+jwzgles_glTexCoord3fv (const GLfloat *v)
+{
+ GLfloat vv[4];
+ vv[0] = v[0];
+ vv[1] = v[1];
+ vv[2] = v[2];
+ vv[3] = 1;
+ jwzgles_glTexCoord4fv (vv);
+}
+
+
+void
+jwzgles_glTexCoord2fv (const GLfloat *v)
+{
+ GLfloat vv[4];
+ vv[0] = v[0];
+ vv[1] = v[1];
+ vv[2] = 0;
+ vv[3] = 1;
+ jwzgles_glTexCoord4fv (vv);
+}
+
+
+void
+jwzgles_glTexCoord3f (GLfloat s, GLfloat t, GLfloat r)
+{
+ jwzgles_glTexCoord4f (s, t, r, 1);
+}
+
+
+void
+jwzgles_glTexCoord2f (GLfloat s, GLfloat t)
+{
+ jwzgles_glTexCoord4f (s, t, 0, 1);
+}
+
+
+void
+jwzgles_glTexCoord1f (GLfloat s)
+{
+ jwzgles_glTexCoord4f (s, 0, 0, 1);
+}
+
+
+/* glColor: GLfloat */
+
+void
+jwzgles_glColor4fv (const GLfloat *v)
+{
+ if (state->compiling_list && !state->compiling_verts)
+ {
+ void_int vv[4];
+ vv[0].f = v[0];
+ vv[1].f = v[1];
+ vv[2].f = v[2];
+ vv[3].f = v[3];
+ list_push ("glColor4f", (list_fn_cb) &jwzgles_glColor4f,
+ PROTO_FFFF, vv);
+ }
+ else
+ {
+ if (!state->replaying_list)
+ LOG6 ("%s%sglColor4f %7.3f %7.3f %7.3f %7.3f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ (state->compiling_verts ? " rec " : ""),
+ v[0], v[1], v[2], v[3]);
+
+ if (state->compiling_verts) /* inside glBegin */
+ {
+ state->set.ccolor.r = v[0];
+ state->set.ccolor.g = v[1];
+ state->set.ccolor.b = v[2];
+ state->set.ccolor.a = v[3];
+ state->set.ccount++;
+ if (state->set.count > 0 && state->set.ccount == 1) /* not first! */
+ state->set.ccount++;
+ }
+ else /* outside glBegin */
+ {
+ glColor4f (v[0], v[1], v[2], v[3]);
+ CHECK("glColor4");
+ }
+ }
+}
+
+
+void
+jwzgles_glColor4f (GLfloat r, GLfloat g, GLfloat b, GLfloat a)
+{
+ GLfloat v[4];
+ v[0] = r;
+ v[1] = g;
+ v[2] = b;
+ v[3] = a;
+ jwzgles_glColor4fv (v);
+}
+
+void
+jwzgles_glColor3f (GLfloat r, GLfloat g, GLfloat b)
+{
+ jwzgles_glColor4f (r, g, b, 1);
+}
+
+void
+jwzgles_glColor3fv (const GLfloat *v)
+{
+ jwzgles_glColor3f (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLdouble */
+
+void
+jwzgles_glColor4d (GLdouble r, GLdouble g, GLdouble b, GLdouble a)
+{
+ jwzgles_glColor4f (r, g, b, a);
+}
+
+void
+jwzgles_glColor4dv (const GLdouble *v)
+{
+ jwzgles_glColor4d (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3d (GLdouble r, GLdouble g, GLdouble b)
+{
+ jwzgles_glColor4d (r, g, b, 1.0);
+}
+
+void
+jwzgles_glColor3dv (const GLdouble *v)
+{
+ jwzgles_glColor3d (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLint (INT_MIN - INT_MAX) */
+
+void
+jwzgles_glColor4i (GLint r, GLint g, GLint b, GLint a)
+{
+ /* -0x8000000 - 0x7FFFFFFF => 0.0 - 1.0 */
+ jwzgles_glColor4f (0.5 + (GLfloat) r / 0xFFFFFFFF,
+ 0.5 + (GLfloat) g / 0xFFFFFFFF,
+ 0.5 + (GLfloat) b / 0xFFFFFFFF,
+ 0.5 + (GLfloat) a / 0xFFFFFFFF);
+}
+
+void
+jwzgles_glColor4iv (const GLint *v)
+{
+ jwzgles_glColor4i (v[0], v[1], v[2], v[3]);
+}
+
+
+void
+jwzgles_glColor3i (GLint r, GLint g, GLint b)
+{
+ jwzgles_glColor4i (r, g, b, 0x7FFFFFFF);
+}
+
+void
+jwzgles_glColor3iv (const GLint *v)
+{
+ jwzgles_glColor3i (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLuint (0 - UINT_MAX) */
+
+void
+jwzgles_glColor4ui (GLuint r, GLuint g, GLuint b, GLuint a)
+{
+ /* 0 - 0xFFFFFFFF => 0.0 - 1.0 */
+ jwzgles_glColor4f ((GLfloat) r / 0xFFFFFFFF,
+ (GLfloat) g / 0xFFFFFFFF,
+ (GLfloat) b / 0xFFFFFFFF,
+ (GLfloat) a / 0xFFFFFFFF);
+}
+
+void
+jwzgles_glColor4uiv (const GLuint *v)
+{
+ jwzgles_glColor4ui (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3ui (GLuint r, GLuint g, GLuint b)
+{
+ jwzgles_glColor4ui (r, g, b, 0xFFFFFFFF);
+}
+
+void
+jwzgles_glColor3uiv (const GLuint *v)
+{
+ jwzgles_glColor3ui (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLshort (SHRT_MIN - SHRT_MAX) */
+
+void
+jwzgles_glColor4s (GLshort r, GLshort g, GLshort b, GLshort a)
+{
+ /* -0x8000 - 0x7FFF => 0.0 - 1.0 */
+ jwzgles_glColor4f (0.5 + (GLfloat) r / 0xFFFF,
+ 0.5 + (GLfloat) g / 0xFFFF,
+ 0.5 + (GLfloat) b / 0xFFFF,
+ 0.5 + (GLfloat) a / 0xFFFF);
+}
+
+void
+jwzgles_glColor4sv (const GLshort *v)
+{
+ jwzgles_glColor4s (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3s (GLshort r, GLshort g, GLshort b)
+{
+ jwzgles_glColor4s (r, g, b, 0x7FFF);
+}
+
+void
+jwzgles_glColor3sv (const GLshort *v)
+{
+ jwzgles_glColor3s (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLushort (0 - USHRT_MAX) */
+
+void
+jwzgles_glColor4us (GLushort r, GLushort g, GLushort b, GLushort a)
+{
+ /* 0 - 0xFFFF => 0.0 - 1.0 */
+ jwzgles_glColor4f ((GLfloat) r / 0xFFFF,
+ (GLfloat) g / 0xFFFF,
+ (GLfloat) b / 0xFFFF,
+ (GLfloat) a / 0xFFFF);
+}
+
+void
+jwzgles_glColor4usv (const GLushort *v)
+{
+ jwzgles_glColor4us (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3us (GLushort r, GLushort g, GLushort b)
+{
+ jwzgles_glColor4us (r, g, b, 0xFFFF);
+}
+
+void
+jwzgles_glColor3usv (const GLushort *v)
+{
+ jwzgles_glColor3us (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLbyte (-128 - 127) */
+
+void
+jwzgles_glColor4b (GLbyte r, GLbyte g, GLbyte b, GLbyte a)
+{
+ /* -128 - 127 => 0.0 - 1.0 */
+ jwzgles_glColor4f (0.5 + (GLfloat) r / 255,
+ 0.5 + (GLfloat) g / 255,
+ 0.5 + (GLfloat) b / 255,
+ 0.5 + (GLfloat) a / 255);
+}
+
+void
+jwzgles_glColor4bv (const GLbyte *v)
+{
+ jwzgles_glColor4b (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3b (GLbyte r, GLbyte g, GLbyte b)
+{
+ jwzgles_glColor4b (r, g, b, 127);
+}
+
+void
+jwzgles_glColor3bv (const GLbyte *v)
+{
+ jwzgles_glColor3b (v[0], v[1], v[2]);
+}
+
+
+/* glColor: GLubyte (0 - 255) */
+
+void
+jwzgles_glColor4ub (GLubyte r, GLubyte g, GLubyte b, GLubyte a)
+{
+ /* 0 - 255 => 0.0 - 1.0 */
+ jwzgles_glColor4f (r / 255.0, g / 255.0, b / 255.0, a / 255.0);
+}
+
+void
+jwzgles_glColor4ubv (const GLubyte *v)
+{
+ jwzgles_glColor4ub (v[0], v[1], v[2], v[3]);
+}
+
+void
+jwzgles_glColor3ub (GLubyte r, GLubyte g, GLubyte b)
+{
+ jwzgles_glColor4ub (r, g, b, 255);
+}
+
+void
+jwzgles_glColor3ubv (const GLubyte *v)
+{
+ jwzgles_glColor3ub (v[0], v[1], v[2]);
+}
+
+
+
+void
+jwzgles_glMaterialfv (GLenum face, GLenum pname, const GLfloat *color)
+{
+ /* If this is called inside glBegin/glEnd with a front ambient color,
+ then treat it the same as glColor: set the color of the upcoming
+ vertex.
+
+ Other faces or lighting types within glBegin are ignored.
+ */
+
+ if (state->compiling_verts)
+ {
+ if ((face == GL_FRONT ||
+ face == GL_FRONT_AND_BACK) &&
+ (pname == GL_AMBIENT ||
+ pname == GL_DIFFUSE ||
+ pname == GL_AMBIENT_AND_DIFFUSE))
+ {
+ jwzgles_glColor4f (color[0], color[1], color[2], color[3]);
+ state->set.materialistic++;
+ }
+ else
+ LOG2 (" IGNORING glMaterialfv %s %s",
+ mode_desc(face), mode_desc(pname));
+ }
+ else if (state->compiling_list)
+ {
+ void_int vv[6];
+ vv[0].i = face;
+ vv[1].i = pname;
+ vv[2].f = color[0];
+ vv[3].f = color[1];
+ vv[4].f = color[2];
+ vv[5].f = color[3];
+ list_push ("glMaterialfv", (list_fn_cb) &jwzgles_glMaterialfv,
+ PROTO_IIFV, vv);
+ }
+ else
+ {
+ /* If this is called outside of glBegin/glEnd with a front
+ ambient color, then the intent is presumably for that color
+ to apply to the upcoming vertexes (which may be played back
+ from a list that does not have vertex colors in it). In that
+ case, the only way to make the colors show up is to call
+ glColor() with GL_COLOR_MATERIAL enabled.
+
+ I'm not sure if this will have other inappropriate side effects...
+ */
+ if ((face == GL_FRONT ||
+ face == GL_FRONT_AND_BACK) &&
+ (pname == GL_AMBIENT ||
+ pname == GL_DIFFUSE ||
+ pname == GL_AMBIENT_AND_DIFFUSE))
+ {
+ jwzgles_glEnable (GL_COLOR_MATERIAL);
+ jwzgles_glColor4f (color[0], color[1], color[2], color[3]);
+ }
+
+ /* OpenGLES seems to throw "invalid enum" for GL_FRONT -- but it
+ goes ahead and sets the material anyway! No error if we just
+ always use GL_FRONT_AND_BACK.
+ */
+ if (face == GL_FRONT)
+ face = GL_FRONT_AND_BACK;
+ if (! state->replaying_list)
+ LOG7 ("direct %-12s %s %s %7.3f %7.3f %7.3f %7.3f", "glMaterialfv",
+ mode_desc(face), mode_desc(pname),
+ color[0], color[1], color[2], color[3]);
+ glMaterialfv (face, pname, color); /* the real one */
+ CHECK("glMaterialfv");
+ }
+}
+
+
+void
+jwzgles_glMaterialiv (GLenum face, GLenum pname, const GLint *v)
+{
+ GLfloat vv[4];
+ vv[0] = v[0];
+ vv[1] = v[1];
+ vv[2] = v[2];
+ vv[3] = 1;
+ jwzgles_glMaterialfv (face, pname, vv);
+}
+
+void
+jwzgles_glMaterialf (GLenum face, GLenum pname, const GLfloat c)
+{
+ GLfloat vv[4];
+ vv[0] = c;
+ vv[1] = c;
+ vv[2] = c;
+ vv[3] = 1;
+ jwzgles_glMaterialfv (face, pname, vv);
+}
+
+
+void
+jwzgles_glMateriali (GLenum face, GLenum pname, const GLuint c)
+{
+ jwzgles_glMaterialf (face, pname, c);
+}
+
+
+void
+jwzgles_glColorMaterial (GLenum face, GLenum mode)
+{
+ Assert (!state->compiling_verts,
+ "glColorMaterial not allowed inside glBegin");
+#if 0
+ if (state->compiling_list)
+ {
+ void_int vv[2];
+ vv[0].i = face;
+ vv[1].i = mode;
+ list_push ("glColorMaterial", (list_fn_cb) &jwzgles_glColorMaterial,
+ PROTO_II, vv);
+ }
+ else
+ {
+ /* No real analog to this distinction in OpenGLES, since color
+ arrays don't distinguish between "color" and "material", */
+ Assert (0, "glColorMaterial: unimplemented mode");
+ }
+#endif
+}
+
+
+
+
+void
+jwzgles_glVertex4fv (const GLfloat *v)
+{
+ vert_set *s = &state->set;
+ int count = s->count;
+
+ Assert (state->compiling_verts, "glVertex4fv not inside glBegin");
+
+ LOG5("%s rec glVertex4f %7.3f %7.3f %7.3f %7.3f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ v[0], v[1], v[2], v[3]);
+
+ if (count >= s->size - 1)
+ {
+ int new_size = 20 + (s->size * 1.2);
+
+ /* 4 arrays, different element sizes...
+ We allocate all 4 arrays just in case we need them,
+ but we might not end up using them all at the end.
+ */
+
+ s->verts = (XYZW *) realloc (s->verts, new_size * sizeof (*s->verts));
+ Assert (s->verts, "out of memory");
+
+ s->norms = (XYZ *) realloc (s->norms, new_size * sizeof (*s->norms));
+ Assert (s->norms, "out of memory");
+
+ s->tex = (STRQ *) realloc (s->tex, new_size * sizeof (*s->tex));
+ Assert (s->tex, "out of memory");
+
+ s->color = (RGBA *) realloc (s->color, new_size * sizeof (*s->color));
+ Assert (s->color, "out of memory");
+
+ s->size = new_size;
+ }
+
+ s->verts [count].x = v[0];
+ s->verts [count].y = v[1];
+ s->verts [count].z = v[2];
+ s->verts [count].w = v[3];
+ s->norms [count] = s->cnorm;
+ s->tex [count] = s->ctex;
+ s->color [count] = s->ccolor;
+ s->count++;
+}
+
+
+void
+jwzgles_glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w)
+{
+ GLfloat v[4];
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+ v[3] = w;
+ jwzgles_glVertex4fv (v);
+}
+
+void
+jwzgles_glVertex4i (GLint x, GLint y, GLint z, GLint w)
+{
+ jwzgles_glVertex4f (x, y, z, w);
+}
+
+void
+jwzgles_glVertex3f (GLfloat x, GLfloat y, GLfloat z)
+{
+ GLfloat v[4];
+ v[0] = x;
+ v[1] = y;
+ v[2] = z;
+ v[3] = 1;
+ jwzgles_glVertex4fv (v);
+}
+
+void
+jwzgles_glVertex3i (GLint x, GLint y, GLint z)
+{
+ jwzgles_glVertex3f (x, y, z);
+}
+
+void
+jwzgles_glVertex3fv (const GLfloat *v)
+{
+ jwzgles_glVertex3f (v[0], v[1], v[2]);
+}
+
+void
+jwzgles_glVertex3dv (const GLdouble *v)
+{
+ jwzgles_glVertex3f (v[0], v[1], v[2]);
+}
+
+
+void
+jwzgles_glVertex2f (GLfloat x, GLfloat y)
+{
+ GLfloat v[3];
+ v[0] = x;
+ v[1] = y;
+ v[2] = 0;
+ jwzgles_glVertex3fv (v);
+}
+
+void
+jwzgles_glVertex2dv (const GLdouble *v)
+{
+ jwzgles_glVertex2f (v[0], v[1]);
+}
+
+void
+jwzgles_glVertex2fv (const GLfloat *v)
+{
+ jwzgles_glVertex2f (v[0], v[1]);
+}
+
+void
+jwzgles_glVertex2i (GLint x, GLint y)
+{
+ jwzgles_glVertex2f (x, y);
+}
+
+
+void
+jwzgles_glLightiv (GLenum light, GLenum pname, const GLint *params)
+{
+ GLfloat v[4];
+ v[0] = params[0];
+ v[1] = params[1];
+ v[2] = params[2];
+ v[3] = params[3];
+ jwzgles_glLightfv (light, pname, v);
+}
+
+void
+jwzgles_glLightModeliv (GLenum pname, const GLint *params)
+{
+ GLfloat v[4];
+ v[0] = params[0];
+ v[1] = params[1];
+ v[2] = params[2];
+ v[3] = params[3];
+ jwzgles_glLightModelfv (pname, v);
+}
+
+void
+jwzgles_glFogiv (GLenum pname, const GLint *params)
+{
+ GLfloat v[4];
+ v[0] = params[0];
+ v[1] = params[1];
+ v[2] = params[2];
+ v[3] = params[3];
+ jwzgles_glFogfv (pname, v);
+}
+
+void
+jwzgles_glLighti (GLenum light, GLenum pname, GLint param)
+{
+ jwzgles_glLightf (light, pname, param);
+}
+
+void
+jwzgles_glLightModeli (GLenum pname, GLint param)
+{
+ jwzgles_glLightModelf (pname, param);
+}
+
+void
+jwzgles_glFogi (GLenum pname, GLint param)
+{
+ jwzgles_glFogf (pname, param);
+}
+
+
+void
+jwzgles_glRotated (GLdouble angle, GLdouble x, GLdouble y, GLdouble z)
+{
+ jwzgles_glRotatef (angle, x, y, z);
+}
+
+
+void
+jwzgles_glClipPlane (GLenum plane, const GLdouble *equation)
+{
+ Assert (state->compiling_verts, "glClipPlane not inside glBegin");
+ Assert (0, "glClipPlane unimplemented"); /* no GLES equivalent... */
+}
+
+
+void
+jwzgles_glPolygonMode (GLenum face, GLenum mode)
+{
+ Assert (!state->compiling_verts, "not inside glBegin");
+ if (state->compiling_list)
+ {
+ void_int vv[2];
+ vv[0].i = face;
+ vv[1].i = mode;
+ list_push ("glPolygonMode", (list_fn_cb) &jwzgles_glPolygonMode,
+ PROTO_II, vv);
+ }
+ else
+ {
+ /* POINT and LINE don't exist in GLES */
+ Assert (mode == GL_FILL, "glPolygonMode: unimplemented mode");
+ }
+}
+
+
+void
+jwzgles_glDrawBuffer (GLenum buf)
+{
+ Assert (!state->compiling_verts, "not inside glBegin");
+ if (state->compiling_list)
+ {
+ void_int vv[1];
+ vv[0].i = buf;
+ list_push ("glDrawBuffer", (list_fn_cb) &jwzgles_glDrawBuffer,
+ PROTO_I, vv);
+ }
+ else
+ {
+/* Assert (buf == GL_BACK, "glDrawBuffer: back buffer only"); */
+# ifndef GL_VERSION_ES_CM_1_0 /* not compiling against OpenGLES 1.x */
+ if (! state->replaying_list)
+ LOG1 ("direct %-12s", "glDrawBuffer");
+ glDrawBuffer (buf); /* the real one */
+ CHECK("glDrawBuffer");
+# endif
+ }
+}
+
+
+/* Given an array of sets of 4 elements of arbitrary size, convert it
+ to an array of sets of 6 elements instead: ABCD becomes ABC BCD.
+ */
+static int
+cq2t (unsigned char **arrayP, int stride, int count)
+{
+ int count2 = count * 6 / 4;
+ int size = stride * count;
+ int size2 = stride * count2;
+ const unsigned char *oarray, *in;
+ unsigned char *array2, *oarray2, *out;
+ int i;
+
+ oarray = *arrayP;
+ if (!oarray || count == 0)
+ return count2;
+
+ array2 = (unsigned char *) malloc (size2);
+ Assert (array2, "out of memory");
+ oarray2 = array2;
+
+ in = oarray;
+ out = oarray2;
+ for (i = 0; i < count / 4; i++)
+ {
+ const unsigned char *a, *b, *c, *d; /* the 4 corners */
+ a = in; in += stride;
+ b = in; in += stride;
+ c = in; in += stride;
+ d = in; in += stride;
+
+# define PUSH(IN) do { \
+ const unsigned char *ii = IN; \
+ int j; \
+ for (j = 0; j < stride; j++) { \
+ *out++ = *ii++; \
+ }} while(0)
+
+ PUSH (a); PUSH (b); PUSH (d); /* the 2 triangles */
+ PUSH (b); PUSH (c); PUSH (d);
+# undef PUSH
+ }
+
+ Assert (in == oarray + size, "convert_quads corrupted");
+ Assert (out == oarray2 + size2, "convert_quads corrupted");
+
+ free (*arrayP);
+ *arrayP = oarray2;
+ return count2;
+}
+
+
+/* Convert all coordinates in a GL_QUADS vert_set to GL_TRIANGLES.
+ */
+static void
+convert_quads_to_triangles (vert_set *s)
+{
+ int count2;
+ Assert (s->mode == GL_QUADS, "convert_quads bad mode");
+ count2 =
+ cq2t ((unsigned char **) &s->verts, sizeof(*s->verts), s->count);
+ cq2t ((unsigned char **) &s->norms, sizeof(*s->norms), s->count);
+ cq2t ((unsigned char **) &s->tex, sizeof(*s->tex), s->count);
+ cq2t ((unsigned char **) &s->color, sizeof(*s->color), s->count);
+ s->count = count2;
+ s->size = count2;
+ s->mode = GL_TRIANGLES;
+}
+
+
+void
+jwzgles_glEnd (void)
+{
+ vert_set *s = &state->set;
+ int was_norm, was_tex, was_color, was_mat;
+ int is_norm, is_tex, is_color, is_mat;
+
+ Assert (state->compiling_verts == 1, "missing glBegin");
+ state->compiling_verts--;
+
+ Assert (!state->replaying_list, "how did glEnd get into a display list?");
+
+ if (!state->replaying_list)
+ {
+ LOG5 ("%s [V = %d, N = %d, T = %d, C = %d]",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ s->count, s->ncount, s->tcount, s->ccount);
+ LOG1 ("%sglEnd",
+ (state->compiling_list || state->replaying_list ? " " : ""));
+ }
+
+ if (s->count == 0) return;
+
+ if (s->mode == GL_QUADS)
+ convert_quads_to_triangles (s);
+ else if (s->mode == GL_QUAD_STRIP)
+ s->mode = GL_TRIANGLE_STRIP; /* They do the same thing! */
+ else if (s->mode == GL_POLYGON)
+ s->mode = GL_TRIANGLE_FAN; /* They do the same thing! */
+
+ jwzgles_glColorPointer (4,GL_FLOAT, sizeof(*s->color),s->color); /* RGBA */
+ jwzgles_glNormalPointer ( GL_FLOAT, sizeof(*s->norms),s->norms); /* XYZ */
+ jwzgles_glTexCoordPointer(4,GL_FLOAT, sizeof(*s->tex), s->tex); /* STRQ */
+ jwzgles_glVertexPointer (4,GL_FLOAT, sizeof(*s->verts),s->verts); /* XYZW */
+ /* glVertexPointer must come after glTexCoordPointer */
+
+ /* If there were no calls to glNormal3f inside of glBegin/glEnd,
+ don't bother enabling the normals array.
+
+ If there was exactly *one* call to glNormal3f inside of glBegin/glEnd,
+ and it was before the first glVertex3f, then also don't enable the
+ normals array, but do emit that call to glNormal3f before calling
+ glDrawArrays.
+
+ Likewise for texture coordinates and colors.
+
+ Be careful to leave the arrays' enabled/disabled state the same as
+ before, or a later caller might end up using one of our arrays by
+ mistake. (Remember that jwzgles_glIsEnabled() tracks the enablement
+ of the list-in-progress as well as the global state.)
+ */
+ was_norm = jwzgles_glIsEnabled (GL_NORMAL_ARRAY);
+ was_tex = jwzgles_glIsEnabled (GL_TEXTURE_COORD_ARRAY);
+ was_color = jwzgles_glIsEnabled (GL_COLOR_ARRAY);
+ was_mat = jwzgles_glIsEnabled (GL_COLOR_MATERIAL);
+
+ /* If we're executing glEnd in immediate mode, not from inside a display
+ list (which is the only way it happens, because glEnd doesn't go into
+ display lists), make sure we're not stomping on a saved buffer list:
+ in immediate mode, vertexes are client-side only.
+ */
+ if (! state->compiling_list)
+ jwzgles_glBindBuffer (GL_ARRAY_BUFFER, 0);
+
+ if (s->ncount > 1)
+ {
+ is_norm = 1;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ }
+ else
+ {
+ is_norm = 0;
+ if (s->ncount == 1)
+ jwzgles_glNormal3f (s->cnorm.x, s->cnorm.y, s->cnorm.z);
+ jwzgles_glDisableClientState (GL_NORMAL_ARRAY);
+ }
+
+ if (s->tcount > 1 ||
+ ((state->compiling_list ? state->list_enabled : state->enabled)
+ & (ISENABLED_TEXTURE_GEN_S | ISENABLED_TEXTURE_GEN_T |
+ ISENABLED_TEXTURE_GEN_R | ISENABLED_TEXTURE_GEN_Q)))
+ {
+ /* Enable texture coords if any were specified; or if generation
+ is on in immediate mode; or if this list turned on generation. */
+ is_tex = 1;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ }
+ else
+ {
+ is_tex = 0;
+ if (s->tcount == 1)
+ jwzgles_glTexCoord4f (s->ctex.s, s->ctex.t, s->ctex.r, s->ctex.q);
+ jwzgles_glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ }
+
+ if (s->ccount > 1)
+ {
+ is_color = 1;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ }
+ else
+ {
+ is_color = 0;
+ if (s->ccount == 1)
+ jwzgles_glColor4f (s->ccolor.r, s->ccolor.g, s->ccolor.b, s->ccolor.a);
+ jwzgles_glDisableClientState (GL_COLOR_ARRAY);
+ }
+
+ jwzgles_glEnableClientState (GL_VERTEX_ARRAY);
+
+ /* We translated the glMaterial calls to per-vertex colors, which are
+ of the glColor sort, not the glMaterial sort, so automatically
+ turn on material mapping. Maybe this is a bad idea.
+ */
+ if (s->materialistic && !jwzgles_glIsEnabled (GL_COLOR_MATERIAL))
+ {
+ is_mat = 1;
+ jwzgles_glEnable (GL_COLOR_MATERIAL);
+ }
+ else
+ is_mat = 0;
+
+ glBindBuffer (GL_ARRAY_BUFFER, 0); /* This comes later. */
+ jwzgles_glDrawArrays (s->mode, 0, s->count);
+ glBindBuffer (GL_ARRAY_BUFFER, 0); /* Keep out of others' hands */
+
+# define RESET(VAR,FN,ARG) do { \
+ if (is_##VAR != was_##VAR) { \
+ if (was_##VAR) jwzgles_glEnable##FN (ARG); \
+ else jwzgles_glDisable##FN (ARG); \
+ }} while(0)
+ RESET (norm, ClientState, GL_NORMAL_ARRAY);
+ RESET (tex, ClientState, GL_TEXTURE_COORD_ARRAY);
+ RESET (color, ClientState, GL_COLOR_ARRAY);
+ RESET (mat, , GL_COLOR_MATERIAL);
+# undef RESET
+
+ s->count = 0;
+ s->ncount = 0;
+ s->tcount = 0;
+ s->ccount = 0;
+ s->materialistic = 0;
+}
+
+
+/* The display list is full of calls to glDrawArrays(), plus saved arrays
+ of the values we need to restore before calling it. "Restore" means
+ "ship them off to the GPU before each call".
+
+ So instead, this function walks through the display list and
+ combines all of those vertex, normal, texture and color values into
+ a single VBO array; ships those values off to the GPU *once* at the
+ time of glEndList; and when running the list with glCallList, the
+ values are already on the GPU and don't need to be sent over again.
+
+ The VBO persists in the GPU until the display list is deleted.
+ */
+static void
+optimize_arrays (void)
+{
+ list *L = &state->lists.lists[state->compiling_list-1];
+ int i, j;
+ GLfloat *combo = 0;
+ int combo_count = 0;
+ int combo_size = 0;
+ GLuint buf_name = 0;
+
+ Assert (state->compiling_list, "not compiling a list");
+ Assert (L, "no list");
+ Assert (!L->buffer, "list already has a buffer");
+
+ glGenBuffers (1, &buf_name);
+ CHECK("glGenBuffers");
+ if (! buf_name) return;
+
+ L->buffer = buf_name;
+
+ /* Go through the list and dump the contents of the various saved arrays
+ into one large array.
+ */
+ for (i = 0; i < L->count; i++)
+ {
+ list_fn *F = &L->fns[i];
+/* int count; */
+ if (! F->arrays)
+ continue;
+/* count = F->argv[2].i;*/ /* 3rd arg to glDrawArrays */
+
+ for (j = 0; j < 4; j++)
+ {
+ draw_array *A = &F->arrays[j];
+ int ocount = combo_count;
+
+ /* If some caller is using arrays that don't have floats in them,
+ we just leave them as-is and ship them over at each call.
+ Doubt this ever really happens.
+ */
+ if (A->type != GL_FLOAT)
+ continue;
+
+ if (! A->data) /* No array. */
+ continue;
+
+ Assert (A->bytes > 0, "no bytes in draw_array");
+ Assert (((unsigned long) A->data > 0xFFFF),
+ "buffer data not a pointer");
+
+ combo_count += A->bytes / sizeof(*combo);
+ make_room ("optimize_arrays",
+ (void **) &combo, sizeof(*combo),
+ &combo_count, &combo_size);
+ memcpy (combo + ocount, A->data, A->bytes);
+ A->binding = buf_name;
+ free (A->data);
+ /* 'data' is now the byte offset into the VBO. */
+ A->data = (void *) (ocount * sizeof(*combo));
+ /* LOG3(" loaded %lu floats to pos %d of buffer %d",
+ A->bytes / sizeof(*combo), ocount, buf_name); */
+ }
+ }
+
+ if (combo_count == 0) /* Nothing to do! */
+ {
+ if (combo) free (combo);
+ glDeleteBuffers (1, &buf_name);
+ L->buffer = 0;
+ return;
+ }
+
+ glBindBuffer (GL_ARRAY_BUFFER, buf_name);
+ glBufferData (GL_ARRAY_BUFFER,
+ combo_count * sizeof (*combo),
+ combo,
+ GL_STATIC_DRAW);
+ glBindBuffer (GL_ARRAY_BUFFER, 0); /* Keep out of others' hands */
+
+ LOG3(" loaded %d floats of list %d into VBO %d",
+ combo_count, state->compiling_list, buf_name);
+
+# ifdef DEBUG
+# if 0
+ for (i = 0; i < combo_count; i++)
+ {
+ if (i % 4 == 0)
+ fprintf (stderr, "\njwzgles: %4d: ", i);
+ fprintf (stderr, " %7.3f", combo[i]);
+ }
+ fprintf (stderr, "\n");
+# endif
+# endif /* DEBUG */
+
+ if (combo) free (combo);
+}
+
+
+void
+jwzgles_glCallList (int id)
+{
+ if (state->compiling_list)
+ {
+ /* Yes, you can call lists inside of lists.
+ Yes, recursion would be a mistake. */
+ void_int vv[1];
+ vv[0].i = id;
+ list_push ("glCallList", (list_fn_cb) &jwzgles_glCallList, PROTO_I, vv);
+ }
+ else
+ {
+ list *L;
+ int i;
+
+ state->replaying_list++;
+
+# ifdef DEBUG
+ fprintf (stderr, "\n");
+ LOG1 ("glCallList %d", id);
+# endif
+
+ Assert (id > 0 && id <= state->lists.count, "glCallList: bogus ID");
+ L = &state->lists.lists[id-1];
+ Assert (id == L->id, "glCallList corrupted");
+
+ for (i = 0; i < L->count; i++)
+ {
+ list_fn *F = &L->fns[i];
+ list_fn_cb fn = F->fn;
+ void_int *av = F->argv;
+
+ switch (F->proto) {
+ case PROTO_VOID:
+ LOG1 (" call %-12s", F->name);
+ ((void (*) (void)) fn) ();
+ break;
+
+ case PROTO_I:
+ if (fn == (list_fn_cb) &jwzgles_glBegin ||
+ fn == (list_fn_cb) &jwzgles_glFrontFace ||
+ fn == (list_fn_cb) &jwzgles_glEnable ||
+ fn == (list_fn_cb) &jwzgles_glDisable ||
+ fn == (list_fn_cb) &jwzgles_glEnableClientState ||
+ fn == (list_fn_cb) &jwzgles_glDisableClientState ||
+ fn == (list_fn_cb) &jwzgles_glShadeModel ||
+ fn == (list_fn_cb) &jwzgles_glMatrixMode)
+ LOG2 (" call %-12s %s", F->name, mode_desc (av[0].i));
+ else
+ LOG2 (" call %-12s %d", F->name, av[0].i);
+ ((void (*) (int)) fn) (av[0].i);
+ break;
+
+ case PROTO_F:
+ LOG2 (" call %-12s %7.3f", F->name, av[0].f);
+ ((void (*) (GLfloat)) fn) (av[0].f);
+ break;
+
+ case PROTO_II:
+ if (fn == (list_fn_cb) &jwzgles_glBindTexture ||
+ fn == (list_fn_cb) &jwzgles_glBindBuffer)
+ LOG3 (" call %-12s %s %d", F->name,
+ mode_desc (av[0].i), av[1].i);
+ else
+ LOG3 (" call %-12s %d %d", F->name, av[0].i, av[1].i);
+ ((void (*) (int, int)) fn) (av[0].i, av[1].i);
+ break;
+
+ case PROTO_FF:
+ LOG3 (" call %-12s %7.3f %7.3f", F->name, av[0].f, av[1].f);
+ ((void (*) (GLfloat, GLfloat)) fn) (av[0].f, av[1].f);
+ break;
+
+ case PROTO_IF:
+ LOG3 (" call %-12s %s %7.3f", F->name,
+ mode_desc (av[0].f), av[1].f);
+ ((void (*) (GLint, GLfloat)) fn) (av[0].i, av[1].f);
+ break;
+
+ case PROTO_III: III:
+ if (fn == (list_fn_cb) &jwzgles_glDrawArrays ||
+ fn == (list_fn_cb) &jwzgles_glTexParameteri)
+ LOG4 (" call %-12s %s %d %d", F->name,
+ mode_desc (av[0].i), av[1].i, av[2].i);
+ else
+ LOG4 (" call %-12s %d %d %d", F->name,
+ av[0].i, av[1].i, av[2].i);
+ ((void (*) (int, int, int)) fn) (av[0].i, av[1].i, av[2].i);
+ break;
+
+ case PROTO_FFF:
+ LOG4 (" call %-12s %7.3f %7.3f %7.3f", F->name,
+ av[0].f, av[1].f, av[2].f);
+ ((void (*) (GLfloat, GLfloat, GLfloat)) fn)
+ (av[0].f, av[1].f, av[2].f);
+ break;
+
+ case PROTO_IIF:
+ LOG4 (" call %-12s %s %s %7.3f", F->name,
+ mode_desc (av[0].i), mode_desc (av[1].i), av[2].f);
+ ((void (*) (int, int, GLfloat)) fn) (av[0].i, av[1].i, av[2].f);
+ break;
+
+ case PROTO_IIII:
+ LOG5 (" call %-12s %d %d %d %d", F->name,
+ av[0].i, av[1].i, av[2].i, av[3].i);
+ ((void (*) (int, int, int, int)) fn)
+ (av[0].i, av[1].i, av[2].i, av[3].i);
+ break;
+
+ case PROTO_FFFF:
+ LOG5 (" call %-12s %7.3f %7.3f %7.3f %7.3f", F->name,
+ av[0].f, av[1].f, av[2].f, av[3].f);
+ ((void (*) (GLfloat, GLfloat, GLfloat, GLfloat)) fn)
+ (av[0].f, av[1].f, av[2].f, av[3].f);
+ break;
+
+ case PROTO_IFV:
+ {
+ GLfloat v[4];
+ v[0] = av[1].f;
+ v[1] = av[2].f;
+ v[2] = av[3].f;
+ v[3] = av[4].f;
+ LOG6 (" call %-12s %s %3.1f %3.1f %3.1f %3.1f", F->name,
+ mode_desc (av[0].i),
+ av[1].f, av[2].f, av[3].f, av[4].f);
+ ((void (*) (int, const GLfloat *)) fn) (av[0].i, v);
+ }
+ break;
+
+ case PROTO_IIFV:
+ {
+ GLfloat v[4];
+ v[0] = av[2].f;
+ v[1] = av[3].f;
+ v[2] = av[4].f;
+ v[3] = av[5].f;
+ LOG7 (" call %-12s %s %-8s %3.1f %3.1f %3.1f %3.1f", F->name,
+ mode_desc (av[0].i), mode_desc (av[1].i),
+ av[2].f, av[3].f, av[4].f, av[5].f);
+ ((void (*) (int, int, const GLfloat *)) fn)
+ (av[0].i, av[1].i, v);
+ }
+ break;
+
+ case PROTO_IIV:
+ {
+ int v[4];
+ v[0] = av[1].i;
+ v[1] = av[2].i;
+ v[2] = av[3].i;
+ v[3] = av[4].i;
+ LOG6 (" call %-12s %s %3d %3d %3d %3d", F->name,
+ mode_desc (av[0].i),
+ av[1].i, av[2].i, av[3].i, av[4].i);
+ ((void (*) (int, const int *)) fn) (av[0].i, v);
+ }
+ break;
+
+ case PROTO_IIIV:
+ {
+ int v[4];
+ v[0] = av[2].i;
+ v[1] = av[3].i;
+ v[2] = av[4].i;
+ v[3] = av[5].i;
+ LOG7 (" call %-12s %s %-8s %3d %3d %3d %3d", F->name,
+ mode_desc (av[0].i), mode_desc (av[1].i),
+ av[2].i, av[3].i, av[4].i, av[5].i);
+ ((void (*) (int, int, const int *)) fn)
+ (av[0].i, av[1].i, v);
+ }
+ break;
+
+ case PROTO_ARRAYS:
+ restore_arrays (F, av[1].i + av[2].i);
+ goto III;
+ break;
+
+ case PROTO_FV16:
+ {
+ GLfloat m[16];
+ int i;
+ for (i = 0; i < countof(m); i++)
+ m[i] = av[i].f;
+ LOG17 (" call %-12s ["
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f " "\n\t\t\t "
+ "%8.3f %8.3f %8.3f %8.3f ]",
+ F->name,
+ m[0], m[1], m[2], m[3],
+ m[4], m[5], m[6], m[7],
+ m[8], m[9], m[10], m[11],
+ m[12], m[13], m[14], m[15]);
+ ((void (*) (GLfloat *)) fn) (m);
+ }
+ break;
+
+ default:
+ Assert (0, "bogus prototype");
+ break;
+ }
+ }
+
+ LOG1 ("glCallList %d done\n", id);
+
+ state->replaying_list--;
+ Assert (state->replaying_list >= 0, "glCallList corrupted");
+ }
+}
+
+
+/* When we save a call to glDrawArrays into a display list, we also need to
+ save the prevailing copy of the arrays that it will use, and restore them
+ later.
+ */
+static void
+save_arrays (list_fn *F, int count)
+{
+ int i = 0;
+ draw_array *A = (draw_array *) calloc (4, sizeof (*A));
+ Assert (A, "out of memory");
+
+/* if (state->set.count > 0) */
+ {
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_BUFFER_BINDING, &A[i].binding);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_SIZE, &A[i].size);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_TYPE, &A[i].type);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_STRIDE, &A[i].stride);
+ jwzgles_glGetPointerv (GL_VERTEX_ARRAY_POINTER, &A[i].data);
+ CHECK("glGetPointerv");
+ copy_array_data (&A[i], count, "vert");
+ }
+
+ i++;
+ if (state->set.ncount > 1)
+ {
+ A[i].size = 3;
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_BUFFER_BINDING, &A[i].binding);
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_TYPE, &A[i].type);
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_STRIDE, &A[i].stride);
+ jwzgles_glGetPointerv (GL_NORMAL_ARRAY_POINTER, &A[i].data);
+ CHECK("glGetPointerv");
+ copy_array_data (&A[i], count, "norm");
+ }
+
+ i++;
+ if (state->set.tcount > 1)
+ {
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, &A[i].binding);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_SIZE, &A[i].size);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_TYPE, &A[i].type);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_STRIDE, &A[i].stride);
+ jwzgles_glGetPointerv (GL_TEXTURE_COORD_ARRAY_POINTER, &A[i].data);
+ CHECK("glGetPointerv");
+ copy_array_data (&A[i], count, "tex ");
+ }
+
+ i++;
+ if (state->set.ccount > 1)
+ {
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_BUFFER_BINDING, &A[i].binding);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_SIZE, &A[i].size);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_TYPE, &A[i].type);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_STRIDE, &A[i].stride);
+ jwzgles_glGetPointerv (GL_COLOR_ARRAY_POINTER, &A[i].data);
+ CHECK("glGetPointerv");
+ copy_array_data (&A[i], count, "col ");
+ }
+
+ /* Freed by glDeleteLists. */
+
+ Assert (!F->arrays, "save_arrays corrupted");
+ F->arrays = A;
+}
+
+
+#ifdef DEBUG
+
+static void
+dump_array_data (draw_array *A, int count,
+ const char *action, const char *name, const void *old)
+{
+ int bytes = count * A->stride;
+
+ if (A->binding)
+ {
+ fprintf (stderr,
+ "jwzgles: %s %s %d %s %2d, %4d = %5d bind %d @ %d\n",
+ action, name,
+ A->size, mode_desc(A->type), A->stride,
+ count, bytes, A->binding, (int) A->data);
+ }
+ else
+ {
+ Assert (bytes == A->bytes, "array data corrupted");
+
+ fprintf (stderr, "jwzgles: %s %s %d %s %2d, %4d = %5d @ %lX",
+ action, name,
+ A->size, mode_desc(A->type), A->stride,
+ count, bytes, (unsigned long) A->data);
+ if (old)
+ fprintf (stderr, " / %lX", (unsigned long) old);
+ fprintf (stderr, "\n");
+ }
+
+ if (A->binding)
+ {
+ Assert (((unsigned long) A->binding < 0xFFFF),
+ "buffer binding should be a numeric index,"
+ " but looks like a pointer");
+
+# if 0
+ /* glGetBufferSubData doesn't actually exist in OpenGLES, but this
+ was helpful for debugging on real OpenGL... */
+ GLfloat *d;
+ int i;
+ fprintf (stderr, "jwzgles: read back:\n");
+ d = (GLfloat *) malloc (A->bytes);
+ glGetBufferSubData (GL_ARRAY_BUFFER, (int) A->data,
+ count * A->stride, (void *) d);
+ CHECK("glGetBufferSubData");
+ for (i = 0; i < count * A->size; i++)
+ {
+ if (i % 4 == 0)
+ fprintf (stderr, "\njwzgles: %4d: ",
+ i + (int) A->data / sizeof(GLfloat));
+ fprintf (stderr, " %7.3f", d[i]);
+ }
+ fprintf (stderr, "\n");
+ free (d);
+# endif
+ }
+# if 0
+ else
+ {
+ unsigned char *b = (unsigned char *) A->data;
+ int i;
+ if ((unsigned long) A->data < 0xFFFF)
+ {
+ Assert (0, "buffer data not a pointer");
+ return;
+ }
+ for (i = 0; i < count; i++)
+ {
+ int j;
+ GLfloat *f = (GLfloat *) b;
+ int s = A->size;
+ if (s == 0) s = 3; /* normals */
+ fprintf (stderr, "jwzgles: ");
+ for (j = 0; j < s; j++)
+ fprintf (stderr, " %7.3f", f[j]);
+ fprintf (stderr, "\n");
+ b += A->stride;
+ }
+ }
+# endif
+}
+
+static void
+dump_direct_array_data (int count)
+{
+ draw_array A = { 0, };
+
+ if (jwzgles_glIsEnabled (GL_VERTEX_ARRAY))
+ {
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_BUFFER_BINDING, &A.binding);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_SIZE, &A.size);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_TYPE, &A.type);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_STRIDE, &A.stride);
+ jwzgles_glGetPointerv (GL_VERTEX_ARRAY_POINTER, &A.data);
+ A.bytes = count * A.stride;
+ dump_array_data (&A, count, "direct", "vertex ", 0);
+ }
+ if (jwzgles_glIsEnabled (GL_NORMAL_ARRAY))
+ {
+ A.size = 0;
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_BUFFER_BINDING, &A.binding);
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_TYPE, &A.type);
+ jwzgles_glGetIntegerv (GL_NORMAL_ARRAY_STRIDE, &A.stride);
+ jwzgles_glGetPointerv (GL_NORMAL_ARRAY_POINTER, &A.data);
+ A.bytes = count * A.stride;
+ dump_array_data (&A, count, "direct", "normal ", 0);
+ }
+ if (jwzgles_glIsEnabled (GL_TEXTURE_COORD_ARRAY))
+ {
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, &A.binding);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_SIZE, &A.size);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_TYPE, &A.type);
+ jwzgles_glGetIntegerv (GL_TEXTURE_COORD_ARRAY_STRIDE, &A.stride);
+ jwzgles_glGetPointerv (GL_TEXTURE_COORD_ARRAY_POINTER, &A.data);
+ A.bytes = count * A.stride;
+ dump_array_data (&A, count, "direct", "texture", 0);
+ }
+ if (jwzgles_glIsEnabled (GL_COLOR_ARRAY))
+ {
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_BUFFER_BINDING, &A.binding);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_SIZE, &A.size);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_TYPE, &A.type);
+ jwzgles_glGetIntegerv (GL_COLOR_ARRAY_STRIDE, &A.stride);
+ jwzgles_glGetPointerv (GL_COLOR_ARRAY_POINTER, &A.data);
+ A.bytes = count * A.stride;
+ dump_array_data (&A, count, "direct", "color ", 0);
+ }
+}
+
+#endif /* DEBUG */
+
+
+static void
+copy_array_data (draw_array *A, int count, const char *name)
+{
+ /* Instead of just memcopy'ing the whole array and obeying its previous
+ 'stride' value, we make up a more compact array. This is because if
+ the same array data is being used with multiple component types,
+ e.g. with glInterleavedArrays, we don't want to copy all of the
+ data multiple times.
+ */
+ int stride2, bytes, i, j;
+ void *data2;
+ const GLfloat *IF;
+ GLfloat *OF;
+ const unsigned char *IB;
+ unsigned char *OB;
+
+ if (((unsigned long) A->data) < 0xFFFF)
+ {
+ Assert (0, "buffer data not a pointer");
+ return;
+ }
+
+ Assert (A->size >= 2 && A->size <= 4, "bogus array size");
+
+ switch (A->type) {
+ case GL_FLOAT: stride2 = A->size * sizeof(GLfloat); break;
+ case GL_UNSIGNED_BYTE: stride2 = A->size; break;
+ default: Assert (0, "bogus array type"); break;
+ }
+
+ bytes = count * stride2;
+ Assert (bytes > 0, "bogus array count or stride");
+ Assert (A->data, "missing array data");
+ data2 = (void *) malloc (bytes);
+ Assert (data2, "out of memory");
+
+ IB = (const unsigned char *) A->data;
+ OB = (unsigned char *) data2;
+ IF = (const GLfloat *) A->data;
+ OF = (GLfloat *) data2;
+
+ switch (A->type) {
+ case GL_FLOAT:
+ for (i = 0; i < count; i++)
+ {
+ for (j = 0; j < A->size; j++)
+ *OF++ = IF[j];
+ IF = (const GLfloat *) (((const unsigned char *) IF) + A->stride);
+ }
+ break;
+ case GL_UNSIGNED_BYTE:
+ for (i = 0; i < count; i++)
+ {
+ for (j = 0; j < A->size; j++)
+ *OB++ = IB[j];
+ IB += A->stride;
+ }
+ break;
+ default:
+ Assert (0, "bogus array type");
+ break;
+ }
+
+ A->data = data2;
+ A->bytes = bytes;
+ A->stride = stride2;
+
+# ifdef DEBUG
+ dump_array_data (A, count, "saved", name, 0);
+# endif
+}
+
+
+static void
+restore_arrays (list_fn *F, int count)
+{
+ int i = 0;
+ draw_array *A = F->arrays;
+ Assert (A, "missing array");
+
+ for (i = 0; i < 4; i++)
+ {
+ const char *name = 0;
+
+ if (!A[i].size)
+ continue;
+
+ Assert ((A[i].binding || A[i].data),
+ "array has neither buffer binding nor data");
+
+ glBindBuffer (GL_ARRAY_BUFFER, A[i].binding);
+ CHECK("glBindBuffer");
+
+ switch (i) {
+ case 0: jwzgles_glVertexPointer (A[i].size, A[i].type, A[i].stride, A[i].data);
+ name = "vertex ";
+ CHECK("glVertexPointer");
+ break;
+ case 1: jwzgles_glNormalPointer ( A[i].type, A[i].stride, A[i].data);
+ name = "normal ";
+ CHECK("glNormalPointer");
+ break;
+ case 2: jwzgles_glTexCoordPointer(A[i].size, A[i].type, A[i].stride, A[i].data);
+ name = "texture";
+ CHECK("glTexCoordPointer");
+ break;
+ case 3: jwzgles_glColorPointer (A[i].size, A[i].type, A[i].stride, A[i].data);
+ name = "color ";
+ CHECK("glColorPointer");
+ break;
+ default: Assert (0, "wat"); break;
+ }
+
+# ifdef DEBUG
+ dump_array_data (&A[i], count, "restored", name, 0);
+# endif
+ (void)name;
+ }
+
+ glBindBuffer (GL_ARRAY_BUFFER, 0); /* Keep out of others' hands */
+}
+
+
+void
+jwzgles_glDrawArrays (GLuint mode, GLuint first, GLuint count)
+{
+ /* If we are auto-generating texture coordinates, do that now, after
+ the vertex array was installed, but before drawing, This happens
+ when recording into a list, or in direct mode. It must happen
+ before calling optimize_arrays() from glEndList().
+ */
+ if (! state->replaying_list &&
+ ((state->compiling_list ? state->list_enabled : state->enabled)
+ & (ISENABLED_TEXTURE_GEN_S | ISENABLED_TEXTURE_GEN_T |
+ ISENABLED_TEXTURE_GEN_R | ISENABLED_TEXTURE_GEN_Q)))
+ generate_texture_coords (first, count);
+
+ if (state->compiling_list)
+ {
+ void_int vv[3];
+ vv[0].i = mode;
+ vv[1].i = first;
+ vv[2].i = count;
+ list_push ("glDrawArrays", (list_fn_cb) &jwzgles_glDrawArrays,
+ PROTO_ARRAYS, vv);
+ }
+ else
+ {
+# ifdef DEBUG
+ if (! state->replaying_list) {
+ LOG4("direct %-12s %d %d %d", "glDrawArrays", mode, first, count);
+ dump_direct_array_data (first + count);
+ }
+# endif
+ glDrawArrays (mode, first, count); /* the real one */
+ CHECK("glDrawArrays");
+ }
+}
+
+
+void
+jwzgles_glInterleavedArrays (GLenum format, GLsizei stride, const void *data)
+{
+ /* We can implement this by calling the various *Pointer functions
+ with offsets into the same data, taking advantage of stride.
+ */
+ const unsigned char *c = (const unsigned char *) data;
+# define B 1
+# define F sizeof(GLfloat)
+
+ Assert (!state->compiling_verts,
+ "glInterleavedArrays not allowed inside glBegin");
+
+ jwzgles_glEnableClientState (GL_VERTEX_ARRAY);
+
+ if (!state->replaying_list)
+ LOG4 ("%sglInterleavedArrays %s %d %lX",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ mode_desc (format), stride, (unsigned long) data);
+
+ switch (format) {
+ case GL_V2F:
+ jwzgles_glVertexPointer (2, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ if (!state->replaying_list)
+ LOG3 ("%s -> glVertexPointer 2 FLOAT %d %lX",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ stride, (unsigned long) c);
+ break;
+ case GL_V3F:
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ if (!state->replaying_list)
+ LOG3 ("%s -> glVertexPointer 3 FLOAT %d %lX",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ stride, (unsigned long) c);
+ break;
+ case GL_C4UB_V2F:
+ if (stride == 0)
+ stride = 4*B + 2*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (4, GL_UNSIGNED_BYTE, stride, c);
+ CHECK("glColorPointer");
+ c += 4*B; /* #### might be incorrect float-aligned address */
+ jwzgles_glVertexPointer (2, GL_FLOAT, stride, c);
+ break;
+ case GL_C4UB_V3F:
+ if (stride == 0)
+ stride = 4*B + 3*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (4, GL_UNSIGNED_BYTE, stride, c);
+ CHECK("glColorPointer");
+ c += 4*B;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_C3F_V3F:
+ if (stride == 0)
+ stride = 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (3, GL_FLOAT, stride, c);
+ CHECK("glColorPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_N3F_V3F:
+ if (stride == 0)
+ stride = 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ jwzgles_glNormalPointer (GL_FLOAT, stride, c);
+ CHECK("glNormalPointer");
+ if (!state->replaying_list)
+ LOG3 ("%s -> glNormalPointer FLOAT %d %lX",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ stride, (unsigned long) c);
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ if (!state->replaying_list)
+ LOG3 ("%s -> glVertexPointer 3 FLOAT %d %lX",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ stride, (unsigned long) c);
+ break;
+ case GL_C4F_N3F_V3F:
+ if (stride == 0)
+ stride = 4*F + 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (4, GL_FLOAT, stride, c);
+ CHECK("glColorPointer");
+ c += 4*F;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ jwzgles_glNormalPointer (GL_FLOAT, stride, c);
+ CHECK("glNormalPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T2F_V3F:
+ if (stride == 0)
+ stride = 2*F + 3*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (2, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 2*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T4F_V4F:
+ if (stride == 0)
+ stride = 4*F + 4*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (4, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 4*F;
+ jwzgles_glVertexPointer (4, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T2F_C4UB_V3F:
+ if (stride == 0)
+ stride = 2*F + 4*B + 3*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (2, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 2*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (4, GL_UNSIGNED_BYTE, stride, c);
+ CHECK("glColorPointer");
+ c += 4*B;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T2F_C3F_V3F:
+ if (stride == 0)
+ stride = 2*F + 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (2, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 2*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (3, GL_FLOAT, stride, c);
+ CHECK("glColorPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T2F_N3F_V3F:
+ if (stride == 0)
+ stride = 2*F + 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (2, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 2*F;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ jwzgles_glNormalPointer (GL_FLOAT, stride, c);
+ CHECK("glNormalPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T2F_C4F_N3F_V3F:
+ if (stride == 0)
+ stride = 2*F + 4*F + 3*F + 3*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (2, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 2*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (3, GL_FLOAT, stride, c);
+ CHECK("glColorPointer");
+ c += 3*F;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ jwzgles_glNormalPointer (GL_FLOAT, stride, c);
+ CHECK("glNormalPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ case GL_T4F_C4F_N3F_V4F:
+ if (stride == 0)
+ stride = 4*F + 4*F + 3*F + 4*F;
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (4, GL_FLOAT, stride, c);
+ CHECK("glTexCoordPointer");
+ c += 4*F;
+ jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+ jwzgles_glColorPointer (4, GL_FLOAT, stride, c);
+ CHECK("glColorPointer");
+ c += 4*F;
+ jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+ jwzgles_glNormalPointer (GL_FLOAT, stride, c);
+ CHECK("glNormalPointer");
+ c += 3*F;
+ jwzgles_glVertexPointer (3, GL_FLOAT, stride, c);
+ CHECK("glVertexPointer");
+ break;
+ default:
+ Assert (0, "glInterleavedArrays: bogus format");
+ break;
+ }
+
+# undef B
+# undef F
+}
+
+
+
+void
+jwzgles_glMultMatrixf (const GLfloat *m)
+{
+ Assert (!state->compiling_verts,
+ "glMultMatrixf not allowed inside glBegin");
+ if (state->compiling_list)
+ {
+ void_int vv[16];
+ int i;
+ for (i = 0; i < countof(vv); i++)
+ vv[i].f = m[i];
+ list_push ("glMultMatrixf", (list_fn_cb) &jwzgles_glMultMatrixf,
+ PROTO_FV16, vv);
+ }
+ else
+ {
+ if (! state->replaying_list)
+ LOG1 ("direct %-12s", "glMultMatrixf");
+ glMultMatrixf (m); /* the real one */
+ CHECK("glMultMatrixf");
+ }
+}
+
+
+void
+jwzgles_glClearIndex(GLfloat c)
+{
+ /* Does GLES even do indexed color? */
+ Assert (0, "glClearIndex unimplemented");
+}
+
+
+void
+jwzgles_glBitmap (GLsizei width, GLsizei height, GLfloat xorig, GLfloat yorig,
+ GLfloat xmove, GLfloat ymove, const GLubyte *bitmap)
+{
+ Assert (0, "glBitmap unimplemented");
+}
+
+void
+jwzgles_glPushAttrib(int flags)
+{
+ Assert (0, "glPushAttrib unimplemented");
+}
+
+void
+jwzgles_glPopAttrib(void)
+{
+ Assert (0, "glPopAttrib unimplemented");
+}
+
+
+/* These are needed for object hit detection in pinion.
+ Might need to rewrite that code entirely. Punt for now.
+ */
+void
+jwzgles_glInitNames (void)
+{
+/* Assert (0, "glInitNames unimplemented");*/
+}
+
+void
+jwzgles_glPushName (GLuint name)
+{
+/* Assert (0, "glPushName unimplemented");*/
+}
+
+GLuint
+jwzgles_glPopName (void)
+{
+/* Assert (0, "glPopName unimplemented");*/
+ return 0;
+}
+
+GLuint
+jwzgles_glRenderMode (GLuint mode)
+{
+/* Assert (0, "glRenderMode unimplemented");*/
+ return 0;
+}
+
+void
+jwzgles_glSelectBuffer (GLsizei size, GLuint *buf)
+{
+/* Assert (0, "glSelectBuffer unimplemented");*/
+}
+
+
+void
+jwzgles_glGenTextures (GLuint n, GLuint *ret)
+{
+ Assert (!state->compiling_verts,
+ "glGenTextures not allowed inside glBegin");
+ /* technically legal, but stupid! */
+ Assert (!state->compiling_list,
+ "glGenTextures not allowed inside glNewList");
+ if (! state->replaying_list)
+ LOG1 ("direct %-12s", "glGenTextures");
+ glGenTextures (n, ret); /* the real one */
+ CHECK("glGenTextures");
+}
+
+
+void
+jwzgles_glTexImage1D (GLenum target, GLint level,
+ GLint internalFormat,
+ GLsizei width, GLint border,
+ GLenum format, GLenum type,
+ const GLvoid *data)
+{
+ Assert (!state->compiling_verts, "glTexImage1D not allowed inside glBegin");
+ /* technically legal, but stupid! */
+ Assert (!state->compiling_list, "glTexImage1D inside glNewList");
+ Assert (width == to_pow2(width), "width must be a power of 2");
+
+ if (target == GL_TEXTURE_1D) target = GL_TEXTURE_2D;
+ jwzgles_glTexImage2D (target, level, internalFormat, width, 1,
+ border, format, type, data);
+}
+
+void
+jwzgles_glTexImage2D (GLenum target,
+ GLint level,
+ GLint internalFormat,
+ GLsizei width,
+ GLsizei height,
+ GLint border,
+ GLenum format,
+ GLenum type,
+ const GLvoid *data)
+{
+ GLvoid *d2 = (GLvoid *) data;
+ Assert (!state->compiling_verts, "glTexImage2D not allowed inside glBegin");
+ Assert (!state->compiling_list, /* technically legal, but stupid! */
+ "glTexImage2D not allowed inside glNewList");
+
+ Assert (width == to_pow2(width), "width must be a power of 2");
+ Assert (height == to_pow2(height), "height must be a power of 2");
+
+ /* OpenGLES no longer supports "4" as a synonym for "RGBA". */
+ switch (internalFormat) {
+ case 1: internalFormat = GL_LUMINANCE; break;
+ case 2: internalFormat = GL_LUMINANCE_ALPHA; break;
+ case 3: internalFormat = GL_RGB; break;
+ case 4: internalFormat = GL_RGBA; break;
+ }
+
+ /* GLES does not let us omit the data pointer to create a blank texture. */
+ if (! data)
+ {
+ d2 = (GLvoid *) calloc (1, width * height * sizeof(GLfloat) * 4);
+ Assert (d2, "out of memory");
+ }
+
+ if (internalFormat == GL_RGB && format == GL_RGBA)
+ internalFormat = GL_RGBA; /* WTF */
+ if (type == GL_UNSIGNED_INT_8_8_8_8_REV)
+ type = GL_UNSIGNED_BYTE;
+
+ if (! state->replaying_list)
+ LOG10 ("direct %-12s %s %d %s %d %d %d %s %s 0x%lX", "glTexImage2D",
+ mode_desc(target), level, mode_desc(internalFormat),
+ width, height, border, mode_desc(format), mode_desc(type),
+ (unsigned long) d2);
+ glTexImage2D (target, level, internalFormat, width, height, border,
+ format, type, d2); /* the real one */
+ CHECK("glTexImage2D");
+
+ if (d2 != data) free (d2);
+}
+
+void
+jwzgles_glTexSubImage2D (GLenum target, GLint level,
+ GLint xoffset, GLint yoffset,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ const GLvoid *pixels)
+{
+ Assert (!state->compiling_verts,
+ "glTexSubImage2D not allowed inside glBegin");
+ Assert (!state->compiling_list, /* technically legal, but stupid! */
+ "glTexSubImage2D not allowed inside glNewList");
+
+ if (! state->replaying_list)
+ LOG10 ("direct %-12s %s %d %d %d %d %d %s %s 0x%lX", "glTexSubImage2D",
+ mode_desc(target), level, xoffset, yoffset, width, height,
+ mode_desc (format), mode_desc (type), (unsigned long) pixels);
+ glTexSubImage2D (target, level, xoffset, yoffset, width, height,
+ format, type, pixels); /* the real one */
+ CHECK("glTexSubImage2D");
+}
+
+void
+jwzgles_glCopyTexImage2D (GLenum target, GLint level, GLenum internalformat,
+ GLint x, GLint y, GLsizei width, GLsizei height,
+ GLint border)
+{
+ Assert (!state->compiling_verts,
+ "glCopyTexImage2D not allowed inside glBegin");
+ Assert (!state->compiling_list, /* technically legal, but stupid! */
+ "glCopyTexImage2D not allowed inside glNewList");
+ if (! state->replaying_list)
+ LOG9 ("direct %-12s %s %d %s %d %d %d %d %d", "glCopyTexImage2D",
+ mode_desc(target), level, mode_desc(internalformat),
+ x, y, width, height, border);
+ glCopyTexImage2D (target, level, internalformat, x, y, width, height,
+ border); /* the real one */
+ CHECK("glCopyTexImage2D");
+}
+
+
+void
+jwzgles_glCopyTexSubImage2D (GLenum target, GLint level,
+ GLint xoff, GLint yoff,
+ GLint x, GLint y,
+ GLsizei width, GLsizei height)
+{
+ Assert (!state->compiling_verts,
+ "glCopyTexSubImage2D not allowed inside glBegin");
+ Assert (!state->compiling_list, /* technically legal, but stupid! */
+ "glCopyTexSubImage2D not allowed inside glNewList");
+ if (! state->replaying_list)
+ LOG9 ("direct %-12s %s %d %d %d %d %d %d %d", "glCopyTexSubImage2D",
+ mode_desc(target), level, xoff, yoff, x, y, width, height);
+ glCopyTexSubImage2D (target, level, /* the real one */
+ xoff, yoff, x, y, width, height);
+ CHECK("glCopyTexSubImage2D");
+}
+
+
+/* OpenGLES doesn't have auto texture-generation at all!
+ "Oh, just rewrite that code to use GPU shaders", they say.
+ How fucking convenient.
+ */
+void
+jwzgles_glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params)
+{
+ texgen_state *s;
+
+ if (pname == GL_TEXTURE_GEN_MODE)
+ LOG5 ("%sdirect %-12s %s %s %s",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ "glTexGenfv",
+ mode_desc(coord), mode_desc(pname), mode_desc(params[0]));
+ else
+ LOG8 ("%sdirect %-12s %s %s %3.1f %3.1f %3.1f %3.1f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ "glTexGenfv",
+ mode_desc(coord), mode_desc(pname),
+ params[0], params[1], params[2], params[3]);
+
+ switch (coord) {
+ case GL_S: s = &state->s; break;
+ case GL_T: s = &state->t; break;
+ case GL_R: s = &state->r; break;
+ case GL_Q: s = &state->q; break;
+ default: Assert (0, "glGetTexGenfv: unknown coord"); break;
+ }
+
+ switch (pname) {
+ case GL_TEXTURE_GEN_MODE: s->mode = params[0]; break;
+ case GL_OBJECT_PLANE: memcpy (s->obj, params, sizeof(s->obj)); break;
+ case GL_EYE_PLANE: memcpy (s->eye, params, sizeof(s->eye)); break;
+ default: Assert (0, "glTexGenfv: unknown pname"); break;
+ }
+}
+
+void
+jwzgles_glTexGeni (GLenum coord, GLenum pname, GLint param)
+{
+ GLfloat v = param;
+ jwzgles_glTexGenfv (coord, pname, &v);
+}
+
+void
+jwzgles_glGetTexGenfv (GLenum coord, GLenum pname, GLfloat *params)
+{
+ texgen_state *s;
+
+ switch (coord) {
+ case GL_S: s = &state->s; break;
+ case GL_T: s = &state->t; break;
+ case GL_R: s = &state->r; break;
+ case GL_Q: s = &state->q; break;
+ default: Assert (0, "glGetTexGenfv: unknown coord"); break;
+ }
+
+ switch (pname) {
+ case GL_TEXTURE_GEN_MODE: params[0] = s->mode; break;
+ case GL_OBJECT_PLANE: memcpy (params, s->obj, sizeof(s->obj)); break;
+ case GL_EYE_PLANE: memcpy (params, s->eye, sizeof(s->eye)); break;
+ default: Assert (0, "glGetTexGenfv: unknown pname"); break;
+ }
+
+ if (pname == GL_TEXTURE_GEN_MODE)
+ LOG5 ("%sdirect %-12s %s %s -> %s",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ "glGetTexGenfv",
+ mode_desc(coord), mode_desc(pname), mode_desc(params[0]));
+ else
+ LOG8 ("%sdirect %-12s %s %s -> %3.1f %3.1f %3.1f %3.1f",
+ (state->compiling_list || state->replaying_list ? " " : ""),
+ "glGetTexGenfv",
+ mode_desc(coord), mode_desc(pname),
+ params[0], params[1], params[2], params[3]);
+}
+
+
+static GLfloat
+dot_product (int rank, GLfloat *a, GLfloat *b)
+{
+ /* A dot B => (A[1] * B[1]) + ... + (A[n] * B[n]) */
+ GLfloat ret = 0;
+ int i;
+ for (i = 0; i < rank; i++)
+ ret += a[i] * b[i];
+ return ret;
+}
+
+
+
+/* Compute the texture coordinates of the prevailing list of verts as per
+ http://www.opengl.org/wiki/Mathematics_of_glTexGen
+ */
+static void
+generate_texture_coords (GLuint first, GLuint count)
+{
+ GLfloat *tex_out, *tex_array;
+ GLsizei tex_stride;
+ GLuint i;
+ draw_array A = { 0, };
+ char *verts_in;
+
+ struct { GLuint which, flag, mode; GLfloat plane[4]; } tg[4] = {
+ { GL_S, ISENABLED_TEXTURE_GEN_S, 0, { 0, } },
+ { GL_T, ISENABLED_TEXTURE_GEN_T, 0, { 0, } },
+ { GL_R, ISENABLED_TEXTURE_GEN_R, 0, { 0, } },
+ { GL_Q, ISENABLED_TEXTURE_GEN_Q, 0, { 0, }}};
+
+ int tcoords = 0;
+
+ /* Read the texture plane configs that were stored with glTexGen.
+ */
+ for (i = 0; i < countof(tg); i++)
+ {
+ GLfloat mode = 0;
+ if (! ((state->compiling_list ? state->list_enabled : state->enabled)
+ & tg[i].flag))
+ continue;
+ jwzgles_glGetTexGenfv (tg[i].which, GL_TEXTURE_GEN_MODE, &mode);
+ jwzgles_glGetTexGenfv (tg[i].which, GL_OBJECT_PLANE, tg[i].plane);
+ tg[i].mode = mode;
+ tcoords++;
+ }
+
+ if (tcoords == 0) return; /* Nothing to do! */
+
+
+ /* Make the array to store our texture coords in. */
+
+ tex_stride = tcoords * sizeof(GLfloat);
+ tex_array = (GLfloat *) calloc (first + count, tex_stride);
+ tex_out = tex_array;
+
+
+ /* Read the prevailing vertex array, that was stored with
+ glVertexPointer or glInterleavedArrays.
+ */
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_BUFFER_BINDING, &A.binding);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_SIZE, &A.size);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_TYPE, &A.type);
+ jwzgles_glGetIntegerv (GL_VERTEX_ARRAY_STRIDE, &A.stride);
+ jwzgles_glGetPointerv (GL_VERTEX_ARRAY_POINTER, &A.data);
+ A.bytes = count * A.stride;
+
+ verts_in = (char *) A.data;
+
+ /* Iterate over each vertex we're drawing.
+ We just skip the ones < start, but the tex array has
+ left room for zeroes there anyway.
+ */
+ for (i = first; i < first + count; i++)
+ {
+ GLfloat vert[4] = { 0, };
+ int j, k;
+
+ /* Extract this vertex into `vert' as a float, whatever its type was. */
+ for (j = 0; j < A.size; j++)
+ {
+ switch (A.type) {
+ case GL_SHORT: vert[j] = ((GLshort *) verts_in)[j]; break;
+ case GL_INT: vert[j] = ((GLint *) verts_in)[j]; break;
+ case GL_FLOAT: vert[j] = ((GLfloat *) verts_in)[j]; break;
+ case GL_DOUBLE: vert[j] = ((GLdouble *) verts_in)[j]; break;
+ default: Assert (0, "unknown vertex type"); break;
+ }
+ }
+
+ /* Compute the texture coordinate for this vertex.
+ For GL_OBJECT_LINEAR, these coordinates are static, and can go
+ into the display list. But for GL_EYE_LINEAR, GL_SPHERE_MAP and
+ GL_REFLECTION_MAP, they depend on the prevailing ModelView matrix,
+ and so need to be computed afresh each time glDrawArrays is called.
+ Unfortunately, our verts and norms are gone by then, dumped down
+ into the VBO and discarded from CPU RAM. Bleh.
+ */
+ for (j = 0, k = 0; j < countof(tg); j++)
+ {
+ if (! ((state->compiling_list ? state->list_enabled : state->enabled)
+ & tg[j].flag))
+ continue;
+ switch (tg[j].mode) {
+ case GL_OBJECT_LINEAR:
+ tex_out[k] = dot_product (4, vert, tg[j].plane);
+ break;
+ default:
+ Assert (0, "unimplemented texture mode");
+ break;
+ }
+ k++;
+ }
+
+ /* fprintf (stderr, "%4d: V %-5.1f %-5.1f %-5.1f T %-5.1f %-5.1f\n",
+ i, vert[0], vert[1], vert[2], tex_out[0], tex_out[1]); */
+
+ /* Move verts_in and tex_out forward to the next vertex by stride. */
+ verts_in += A.stride;
+ tex_out = (GLfloat *) (((char *) tex_out) + tex_stride);
+ }
+
+ jwzgles_glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ jwzgles_glTexCoordPointer (tcoords, GL_FLOAT, tex_stride,
+ (GLvoid *) tex_array);
+ free (tex_array);
+}
+
+
+int
+jwzgles_gluBuild2DMipmaps (GLenum target,
+ GLint internalFormat,
+ GLsizei width,
+ GLsizei height,
+ GLenum format,
+ GLenum type,
+ const GLvoid *data)
+{
+ /* Not really bothering with mipmapping; only making one level.
+ Note that this required a corresponding hack in glTexParameterf().
+ */
+
+ GLsizei w2 = (GLsizei)to_pow2(width);
+ GLsizei h2 = (GLsizei)to_pow2(height);
+
+ void *d2 = (void *) data;
+
+ /* OpenGLES no longer supports "4" as a synonym for "RGBA". */
+ switch (internalFormat) {
+ case 1: internalFormat = GL_LUMINANCE; break;
+ case 2: internalFormat = GL_LUMINANCE_ALPHA; break;
+ case 3: internalFormat = GL_RGB; break;
+ case 4: internalFormat = GL_RGBA; break;
+ }
+
+/* if (w2 < h2) w2 = h2;
+ if (h2 < w2) h2 = w2;*/
+
+ if (w2 != width || h2 != height)
+ {
+ /* Scale up the image bits to fit the power-of-2 texture.
+ We have to do this because the mipmap API assumes that
+ the texture bits go to texture coordinates 1.0 x 1.0.
+ This could be more efficient, but it doesn't happen often.
+ */
+ int istride = (format == GL_RGBA ? 4 : 3);
+ int ostride = 4;
+ int ibpl = istride * width;
+ int obpl = ostride * w2;
+ int oy;
+ const unsigned char *in = (unsigned char *) data;
+ unsigned char *out = (void *) malloc (h2 * obpl);
+ Assert (out, "out of memory");
+ d2 = out;
+
+ for (oy = 0; oy < h2; oy++)
+ {
+ int iy = oy * height / h2;
+ const unsigned char *iline = in + (iy * ibpl);
+ unsigned char *oline = out + (oy * obpl);
+ int ox;
+ for (ox = 0; ox < w2; ox++)
+ {
+ int ix = ox * width / w2;
+ const unsigned char *i = iline + (ix * istride);
+ unsigned char *o = oline + (ox * ostride);
+ *o++ = *i++; /* R */
+ *o++ = *i++; /* G */
+ *o++ = *i++; /* B */
+ *o++ = (istride == 4 ? *i : 0xFF); /* A */
+ }
+ }
+ /* width = w2; */
+ /* height = h2; */
+ internalFormat = GL_RGBA;
+ format = GL_RGBA;
+ }
+
+ jwzgles_glTexImage2D (target, 0, internalFormat, w2, h2, 0,
+ format, type, d2);
+ if (d2 != data) free (d2);
+
+ return 0;
+}
+
+
+void
+jwzgles_glRectf (GLfloat x1, GLfloat y1, GLfloat x2, GLfloat y2)
+{
+ jwzgles_glBegin (GL_POLYGON);
+ jwzgles_glVertex2f (x1, y1);
+ jwzgles_glVertex2f (x2, y1);
+ jwzgles_glVertex2f (x2, y2);
+ jwzgles_glVertex2f (x1, y2);
+ jwzgles_glEnd ();
+}
+
+void
+jwzgles_glRecti (GLint x1, GLint y1, GLint x2, GLint y2)
+{
+ jwzgles_glRectf (x1, y1, x2, y2);
+}
+
+void
+jwzgles_glClearDepth (GLfloat d)
+{
+ /* Not sure what to do here */
+ Assert (d == 1.0, "glClearDepth unimplemented");
+}
+
+
+/* When in immediate mode, we store a bit into state->enabled, and also
+ call the real glEnable() / glDisable().
+
+ When recording a list, we store a bit into state->list_enabled instead,
+ so that we can see what the prevailing enablement state will be when
+ the list is run.
+
+ set: 1 = set, -1 = clear, 0 = query.
+*/
+static int
+enable_disable (GLuint bit, int set)
+{
+ int result = (set > 0);
+ int omitp = 0;
+ int csp = 0;
+ unsigned long flag = 0;
+
+ switch (bit) {
+ case GL_TEXTURE_1D: /* We implement 1D textures as 2D textures. */
+ case GL_TEXTURE_2D: flag = ISENABLED_TEXTURE_2D; break;
+ case GL_TEXTURE_GEN_S: flag = ISENABLED_TEXTURE_GEN_S; omitp = 1; break;
+ case GL_TEXTURE_GEN_T: flag = ISENABLED_TEXTURE_GEN_T; omitp = 1; break;
+ case GL_TEXTURE_GEN_R: flag = ISENABLED_TEXTURE_GEN_R; omitp = 1; break;
+ case GL_TEXTURE_GEN_Q: flag = ISENABLED_TEXTURE_GEN_Q; omitp = 1; break;
+ case GL_LIGHTING: flag = ISENABLED_LIGHTING; break;
+ case GL_BLEND: flag = ISENABLED_BLEND; break;
+ case GL_DEPTH_TEST: flag = ISENABLED_DEPTH_TEST; break;
+ case GL_ALPHA_TEST: flag = ISENABLED_ALPHA_TEST; break;
+ case GL_CULL_FACE: flag = ISENABLED_CULL_FACE; break;
+ case GL_NORMALIZE: flag = ISENABLED_NORMALIZE; break;
+ case GL_FOG: flag = ISENABLED_FOG; break;
+ case GL_COLOR_MATERIAL: flag = ISENABLED_COLMAT; break;
+
+ /* Maybe technically these only work with glEnableClientState,
+ but we treat that as synonymous with glEnable. */
+ case GL_VERTEX_ARRAY: flag = ISENABLED_VERT_ARRAY; csp = 1; break;
+ case GL_NORMAL_ARRAY: flag = ISENABLED_NORM_ARRAY; csp = 1; break;
+ case GL_COLOR_ARRAY: flag = ISENABLED_COLOR_ARRAY; csp = 1; break;
+ case GL_TEXTURE_COORD_ARRAY: flag = ISENABLED_TEX_ARRAY; csp = 1; break;
+
+ default:
+ Assert (set != 0, "glIsEnabled unimplemented bit");
+ break;
+ }
+
+ if (set) /* setting or unsetting, not querying */
+ {
+ const char *fns[4] = { "glEnable", "glDisable",
+ "glEnableClientState", "glDisableClientState" };
+ list_fn_cb fs[4] = { (list_fn_cb) &jwzgles_glEnable,
+ (list_fn_cb) &jwzgles_glDisable,
+ (list_fn_cb) &jwzgles_glEnableClientState,
+ (list_fn_cb) &jwzgles_glDisableClientState };
+ const char *fn = fns[(csp ? 2 : 0) + (set < 0 ? 1 : 0)];
+ list_fn_cb f = fs[(csp ? 2 : 0) + (set < 0 ? 1 : 0)];
+
+ Assert (!state->compiling_verts,
+ "glEnable/glDisable not allowed inside glBegin");
+
+ if (state->compiling_list)
+ {
+ void_int vv[1];
+ vv[0].i = bit;
+ list_push (fn, f,PROTO_I, vv);
+ }
+
+ if (! state->replaying_list &&
+ ! state->compiling_list)
+ LOG2 ("direct %-12s %s", fn, mode_desc(bit));
+
+ if (csp && !state->compiling_verts)
+ {
+ if (set > 0)
+ switch (bit) {
+ case GL_NORMAL_ARRAY: state->set.ncount += 2; break;
+ case GL_TEXTURE_COORD_ARRAY: state->set.tcount += 2; break;
+ case GL_COLOR_ARRAY: state->set.ccount += 2; break;
+ default: break;
+ }
+ else
+ switch (bit) {
+ case GL_NORMAL_ARRAY: state->set.ncount = 0; break;
+ case GL_TEXTURE_COORD_ARRAY: state->set.tcount = 0; break;
+ case GL_COLOR_ARRAY: state->set.ccount = 0; break;
+ default: break;
+ }
+ }
+
+ if (omitp || state->compiling_list)
+ ;
+ else if (set > 0 && csp)
+ glEnableClientState (bit); /* the real one */
+ else if (set < 0 && csp)
+ glDisableClientState (bit); /* the real one */
+ else if (set > 0)
+ glEnable (bit); /* the real one */
+ else
+ glDisable (bit); /* the real one */
+
+ CHECK(fn);
+ }
+
+ /* Store the bit in our state as well, or query it.
+ */
+ if (flag)
+ {
+ unsigned long *enabled = (state->compiling_list
+ ? &state->list_enabled
+ : &state->enabled);
+ if (set > 0)
+ *enabled |= flag;
+ else if (set < 0)
+ *enabled &= ~flag;
+ else
+ result = !!(*enabled & flag);
+ }
+
+ return result;
+}
+
+
+void
+jwzgles_glEnable (GLuint bit)
+{
+ enable_disable (bit, 1);
+}
+
+void
+jwzgles_glDisable (GLuint bit)
+{
+ enable_disable (bit, -1);
+}
+
+GLboolean
+jwzgles_glIsEnabled (GLuint bit)
+{
+ return enable_disable (bit, 0);
+}
+
+void
+jwzgles_glEnableClientState (GLuint cap)
+{
+ enable_disable (cap, 1);
+}
+
+void
+jwzgles_glDisableClientState (GLuint cap)
+{
+ enable_disable (cap, -1);
+}
+
+
+#define GET(pname, value) \
+ case pname: \
+ *params = value; \
+ break;
+
+/* The spec says that OpenGLES 1.0 doesn't implement glGetFloatv.
+
+ iOS provides 1.1 (and glGetFloatv by extension) at the very minimum.
+
+ Android goes down to 1.0. In particular, this includes the emulator when
+ running without GPU emulation. Actual devices that don't support 1.1 are
+ extremely rare at this point.
+
+ OpenGL ES 1.0 sucks because without glGetFloatv there is no way to retrieve
+ the prevailing matrixes. To implement this, we'd have to keep track of
+ them all on the client side by combining in all the actions of
+ glMultMatrixf, glRotatef, etc. Right now, we're only keeping track of the
+ gl*Pointer functions.
+ */
+void
+jwzgles_glGetFloatv (GLenum pname, GLfloat *params)
+{
+ if (! state->replaying_list)
+ LOG2 ("direct %-12s %s", "glGetFloatv", mode_desc(pname));
+
+ switch (pname)
+ {
+ /* OpenGL ES 1.0 omits a few dozen properties that 1.1 supports. The
+ following, however, is sufficient to get things basically working in the
+ Android emulator.
+ */
+
+ GET(GL_VERTEX_ARRAY_BUFFER_BINDING, state->varray.binding)
+ GET(GL_VERTEX_ARRAY_SIZE, state->varray.size)
+ GET(GL_VERTEX_ARRAY_TYPE, state->varray.type)
+ GET(GL_VERTEX_ARRAY_STRIDE, state->varray.stride)
+
+ GET(GL_NORMAL_ARRAY_BUFFER_BINDING, state->narray.binding)
+ GET(GL_NORMAL_ARRAY_TYPE, state->narray.type)
+ GET(GL_NORMAL_ARRAY_STRIDE, state->narray.stride)
+
+ GET(GL_COLOR_ARRAY_BUFFER_BINDING, state->carray.binding)
+ GET(GL_COLOR_ARRAY_SIZE, state->carray.size)
+ GET(GL_COLOR_ARRAY_TYPE, state->carray.type)
+ GET(GL_COLOR_ARRAY_STRIDE, state->carray.stride)
+
+ GET(GL_TEXTURE_COORD_ARRAY_BUFFER_BINDING, state->tarray.binding)
+ GET(GL_TEXTURE_COORD_ARRAY_SIZE, state->tarray.size)
+ GET(GL_TEXTURE_COORD_ARRAY_TYPE, state->tarray.type)
+ GET(GL_TEXTURE_COORD_ARRAY_STRIDE, state->tarray.stride)
+
+ default:
+ glGetFloatv (pname, params); /* the real one */
+ break;
+ }
+
+ CHECK("glGetFloatv");
+}
+
+
+void
+jwzgles_glGetPointerv (GLenum pname, GLvoid **params)
+{
+ if (! state->replaying_list)
+ LOG2 ("direct %-12s %s", "glGetPointerv", mode_desc(pname));
+
+ switch (pname)
+ {
+ GET(GL_VERTEX_ARRAY_POINTER, state->varray.data)
+ GET(GL_NORMAL_ARRAY_POINTER, state->narray.data)
+ GET(GL_COLOR_ARRAY_POINTER, state->carray.data)
+ GET(GL_TEXTURE_COORD_ARRAY_POINTER, state->tarray.data)
+
+ default:
+ glGetPointerv (pname, params); /* the real one */
+ }
+
+ CHECK("glGetPointerv");
+}
+
+#undef GET
+
+
+/* How many cells are written into the *params array.
+ We need to know this to avoid smashing the caller's stack
+ if they asked for a single-value parameter.
+ */
+static int
+glGet_ret_count (GLenum pname)
+{
+ switch (pname) {
+/*case GL_COLOR_MATRIX: */
+ case GL_MODELVIEW_MATRIX:
+ case GL_PROJECTION_MATRIX:
+ case GL_TEXTURE_MATRIX:
+/*case GL_TRANSPOSE_COLOR_MATRIX: */
+/*case GL_TRANSPOSE_MODELVIEW_MATRIX: */
+/*case GL_TRANSPOSE_PROJECTION_MATRIX: */
+/*case GL_TRANSPOSE_TEXTURE_MATRIX: */
+ return 16;
+/*case GL_ACCUM_CLEAR_VALUE: */
+/*case GL_BLEND_COLOR: */
+ case GL_COLOR_CLEAR_VALUE:
+ case GL_COLOR_WRITEMASK:
+ case GL_CURRENT_COLOR:
+/*case GL_CURRENT_RASTER_COLOR: */
+/*case GL_CURRENT_RASTER_POSITION: */
+/*case GL_CURRENT_RASTER_SECONDARY_COLOR: */
+/*case GL_CURRENT_RASTER_TEXTURE_COORDS: */
+/*case GL_CURRENT_SECONDARY_COLOR: */
+ case GL_CURRENT_TEXTURE_COORDS:
+ case GL_FOG_COLOR:
+ case GL_LIGHT_MODEL_AMBIENT:
+/*case GL_MAP2_GRID_DOMAIN: */
+ case GL_SCISSOR_BOX:
+ case GL_VIEWPORT:
+ return 4;
+ case GL_CURRENT_NORMAL:
+ case GL_POINT_DISTANCE_ATTENUATION:
+ return 3;
+ case GL_ALIASED_LINE_WIDTH_RANGE:
+ case GL_ALIASED_POINT_SIZE_RANGE:
+ case GL_DEPTH_RANGE:
+/*case GL_LINE_WIDTH_RANGE: */
+/*case GL_MAP1_GRID_DOMAIN: */
+/*case GL_MAP2_GRID_SEGMENTS: */
+ case GL_MAX_VIEWPORT_DIMS:
+/*case GL_POINT_SIZE_RANGE: */
+ case GL_POLYGON_MODE:
+ case GL_SMOOTH_LINE_WIDTH_RANGE:
+ case GL_SMOOTH_POINT_SIZE_RANGE:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+
+void
+jwzgles_glGetDoublev (GLenum pname, GLdouble *params)
+{
+ GLfloat m[16];
+ int i, j = glGet_ret_count (pname);
+ jwzgles_glGetFloatv (pname, m);
+ for (i = 0; i < j; i++)
+ params[i] = m[i];
+}
+
+
+void
+jwzgles_glGetIntegerv (GLenum pname, GLint *params)
+{
+ GLfloat m[16];
+ int i, j = glGet_ret_count (pname);
+ jwzgles_glGetFloatv (pname, m);
+ for (i = 0; i < j; i++)
+ params[i] = m[i];
+}
+
+
+void
+jwzgles_glGetBooleanv (GLenum pname, GLboolean *params)
+{
+ GLfloat m[16];
+ int i, j = glGet_ret_count (pname);
+ jwzgles_glGetFloatv (pname, m);
+ for (i = 0; i < j; i++)
+ params[i] = (m[i] != 0.0);
+}
+
+
+const char *
+jwzgles_gluErrorString (GLenum error)
+{
+ static char s[20];
+ sprintf (s, "0x%lX", (unsigned long) error);
+ return s;
+}
+
+
+/* These four *Pointer calls (plus glBindBuffer and glBufferData) can
+ be included inside glNewList, but they actually execute immediately
+ anyway, because their data is recorded in the list by the
+ subsequently-recorded call to glDrawArrays. This is a little weird.
+ */
+void
+jwzgles_glVertexPointer (GLuint size, GLuint type, GLuint stride,
+ const GLvoid *ptr)
+{
+ if (! state->replaying_list)
+ LOG5 ("direct %-12s %d %s %d 0x%lX", "glVertexPointer",
+ size, mode_desc(type), stride, (unsigned long) ptr);
+
+ state->varray.size = size;
+ state->varray.type = type;
+ state->varray.stride = stride;
+ state->varray.data = (GLvoid *)ptr;
+
+ glVertexPointer (size, type, stride, ptr); /* the real one */
+ CHECK("glVertexPointer");
+}
+
+
+void
+jwzgles_glNormalPointer (GLuint type, GLuint stride, const GLvoid *ptr)
+{
+ if (! state->replaying_list)
+ LOG4 ("direct %-12s %s %d 0x%lX", "glNormalPointer",
+ mode_desc(type), stride, (unsigned long) ptr);
+
+ state->narray.type = type;
+ state->narray.stride = stride;
+ state->narray.data = (GLvoid *)ptr;
+
+ glNormalPointer (type, stride, ptr); /* the real one */
+ CHECK("glNormalPointer");
+}
+
+void
+jwzgles_glColorPointer (GLuint size, GLuint type, GLuint stride,
+ const GLvoid *ptr)
+{
+ if (! state->replaying_list)
+ LOG5 ("direct %-12s %d %s %d 0x%lX", "glColorPointer",
+ size, mode_desc(type), stride, (unsigned long) ptr);
+
+ state->carray.size = size;
+ state->carray.type = type;
+ state->carray.stride = stride;
+ state->carray.data = (GLvoid *)ptr;
+
+ glColorPointer (size, type, stride, ptr); /* the real one */
+ CHECK("glColorPointer");
+}
+
+void
+jwzgles_glTexCoordPointer (GLuint size, GLuint type, GLuint stride,
+ const GLvoid *ptr)
+{
+ if (! state->replaying_list)
+ LOG5 ("direct %-12s %d %s %d 0x%lX", "glTexCoordPointer",
+ size, mode_desc(type), stride, (unsigned long) ptr);
+
+ state->tarray.size = size;
+ state->tarray.type = type;
+ state->tarray.stride = stride;
+ state->tarray.data = (GLvoid *)ptr;
+
+ glTexCoordPointer (size, type, stride, ptr); /* the real one */
+ CHECK("glTexCoordPointer");
+}
+
+void
+jwzgles_glBindBuffer (GLuint target, GLuint buffer)
+{
+ if (! state->replaying_list)
+ LOG3 ("direct %-12s %s %d", "glBindBuffer", mode_desc(target), buffer);
+ glBindBuffer (target, buffer); /* the real one */
+ CHECK("glBindBuffer");
+}
+
+void
+jwzgles_glBufferData (GLenum target, GLsizeiptr size, const void *data,
+ GLenum usage)
+{
+ if (! state->replaying_list)
+ LOG5 ("direct %-12s %s %ld 0x%lX %s", "glBufferData",
+ mode_desc(target), size, (unsigned long) data, mode_desc(usage));
+ glBufferData (target, size, data, usage); /* the real one */
+ CHECK("glBufferData");
+}
+
+
+void
+jwzgles_glTexParameterf (GLuint target, GLuint pname, GLfloat param)
+{
+ Assert (!state->compiling_verts,
+ "glTexParameterf not allowed inside glBegin");
+
+ /* We don't *really* implement mipmaps, so just turn this off. */
+ if (param == GL_LINEAR_MIPMAP_LINEAR) param = GL_LINEAR;
+ if (param == GL_NEAREST_MIPMAP_LINEAR) param = GL_LINEAR;
+ if (param == GL_LINEAR_MIPMAP_NEAREST) param = GL_NEAREST;
+ if (param == GL_NEAREST_MIPMAP_NEAREST) param = GL_NEAREST;
+
+ /* We implement 1D textures as 2D textures. */
+ if (target == GL_TEXTURE_1D) target = GL_TEXTURE_2D;
+
+ /* Apparently this is another invalid enum. Just ignore it. */
+ if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) &&
+ param == GL_CLAMP)
+ return;
+
+ if (state->compiling_list)
+ {
+ void_int vv[3];
+ vv[0].i = target;
+ vv[1].i = pname;
+ vv[2].f = param;
+ list_push ("glTexParameterf", (list_fn_cb) &jwzgles_glTexParameterf,
+ PROTO_IIF, vv);
+ }
+ else
+ {
+ if (! state->replaying_list)
+ LOG4 ("direct %-12s %s %s %7.3f", "glTexParameterf",
+ mode_desc(target), mode_desc(pname), param);
+ glTexParameterf (target, pname, param); /* the real one */
+ CHECK("glTexParameterf");
+ }
+}
+
+void
+jwzgles_glTexParameteri (GLuint target, GLuint pname, GLuint param)
+{
+ jwzgles_glTexParameterf (target, pname, param);
+}
+
+
+void
+jwzgles_glBindTexture (GLuint target, GLuint texture)
+{
+ Assert (!state->compiling_verts,
+ "glBindTexture not allowed inside glBegin");
+
+ /* We implement 1D textures as 2D textures. */
+ if (target == GL_TEXTURE_1D) target = GL_TEXTURE_2D;
+
+ if (state->compiling_list)
+ {
+ void_int vv[2];
+ vv[0].i = target;
+ vv[1].i = texture;
+ list_push ("glBindTexture", (list_fn_cb) &jwzgles_glBindTexture,
+ PROTO_II, vv);
+ }
+
+ /* Do it immediately as well, for generate_texture_coords */
+ /* else */
+ {
+ if (! state->replaying_list)
+ LOG3 ("direct %-12s %s %d", "glBindTexture",
+ mode_desc(target), texture);
+ glBindTexture (target, texture); /* the real one */
+ CHECK("glBindTexture");
+ }
+}
+
+
+
+/* Matrix functions, mostly cribbed from Mesa.
+ */
+
+void
+jwzgles_glFrustum (GLfloat left, GLfloat right,
+ GLfloat bottom, GLfloat top,
+ GLfloat near, GLfloat far)
+{
+ GLfloat m[16];
+ GLfloat x = (2 * near) / (right-left);
+ GLfloat y = (2 * near) / (top - bottom);
+ GLfloat a = (right + left) / (right - left);
+ GLfloat b = (top + bottom) / (top - bottom);
+ GLfloat c = -(far + near) / (far - near);
+ GLfloat d = -(2 * far * near) / (far - near);
+
+# define M(X,Y) m[Y * 4 + X]
+ M(0,0) = x; M(0,1) = 0; M(0,2) = a; M(0,3) = 0;
+ M(1,0) = 0; M(1,1) = y; M(1,2) = b; M(1,3) = 0;
+ M(2,0) = 0; M(2,1) = 0; M(2,2) = c; M(2,3) = d;
+ M(3,0) = 0; M(3,1) = 0; M(3,2) = -1; M(3,3) = 0;
+# undef M
+
+ jwzgles_glMultMatrixf (m);
+}
+
+
+void
+jwzgles_glOrtho (GLfloat left, GLfloat right,
+ GLfloat bottom, GLfloat top,
+ GLfloat near, GLfloat far)
+{
+ GLfloat m[16];
+ GLfloat a = 2 / (right - left);
+ GLfloat b = -(right + left) / (right - left);
+ GLfloat c = 2 / (top - bottom);
+ GLfloat d = -(top + bottom) / (top - bottom);
+ GLfloat e = -2 / (far - near);
+ GLfloat f = -(far + near) / (far - near);
+
+# define M(X,Y) m[Y * 4 + X]
+ M(0,0) = a; M(0,1) = 0; M(0,2) = 0; M(0,3) = b;
+ M(1,0) = 0; M(1,1) = c; M(1,2) = 0; M(1,3) = d;
+ M(2,0) = 0; M(2,1) = 0; M(2,2) = e; M(2,3) = f;
+ M(3,0) = 0; M(3,1) = 0; M(3,2) = 0; M(3,3) = 1;
+# undef M
+
+ jwzgles_glMultMatrixf (m);
+}
+
+
+void
+jwzgles_gluPerspective (GLdouble fovy, GLdouble aspect,
+ GLdouble near, GLdouble far)
+{
+ GLfloat m[16];
+ double si, co, dz;
+ double rad = fovy / 2 * M_PI / 180;
+ double a, b, c, d;
+
+ dz = far - near;
+ si = sin(rad);
+ if (dz == 0 || si == 0 || aspect == 0)
+ return;
+ co = cos(rad) / si;
+
+ a = co / aspect;
+ b = co;
+ c = -(far + near) / dz;
+ d = -2 * near * far / dz;
+
+# define M(X,Y) m[Y * 4 + X]
+ M(0,0) = a; M(0,1) = 0; M(0,2) = 0; M(0,3) = 0;
+ M(1,0) = 0; M(1,1) = b; M(1,2) = 0; M(1,3) = 0;
+ M(2,0) = 0; M(2,1) = 0; M(2,2) = c; M(2,3) = d;
+ M(3,0) = 0; M(3,1) = 0; M(3,2) = -1; M(3,3) = 0;
+# undef M
+
+ jwzgles_glMultMatrixf (m);
+}
+
+
+void
+jwzgles_gluLookAt (GLfloat eyex, GLfloat eyey, GLfloat eyez,
+ GLfloat centerx, GLfloat centery, GLfloat centerz,
+ GLfloat upx, GLfloat upy, GLfloat upz)
+{
+ GLfloat m[16];
+ GLfloat x[3], y[3], z[3];
+ GLfloat mag;
+
+ /* Make rotation matrix */
+
+ /* Z vector */
+ z[0] = eyex - centerx;
+ z[1] = eyey - centery;
+ z[2] = eyez - centerz;
+ mag = sqrt(z[0] * z[0] + z[1] * z[1] + z[2] * z[2]);
+ if (mag) { /* mpichler, 19950515 */
+ z[0] /= mag;
+ z[1] /= mag;
+ z[2] /= mag;
+ }
+
+ /* Y vector */
+ y[0] = upx;
+ y[1] = upy;
+ y[2] = upz;
+
+ /* X vector = Y cross Z */
+ x[0] = y[1] * z[2] - y[2] * z[1];
+ x[1] = -y[0] * z[2] + y[2] * z[0];
+ x[2] = y[0] * z[1] - y[1] * z[0];
+
+ /* Recompute Y = Z cross X */
+ y[0] = z[1] * x[2] - z[2] * x[1];
+ y[1] = -z[0] * x[2] + z[2] * x[0];
+ y[2] = z[0] * x[1] - z[1] * x[0];
+
+ /* mpichler, 19950515 */
+ /* cross product gives area of parallelogram, which is < 1.0 for
+ * non-perpendicular unit-length vectors; so normalize x, y here
+ */
+
+ mag = sqrt(x[0] * x[0] + x[1] * x[1] + x[2] * x[2]);
+ if (mag) {
+ x[0] /= mag;
+ x[1] /= mag;
+ x[2] /= mag;
+ }
+
+ mag = sqrt(y[0] * y[0] + y[1] * y[1] + y[2] * y[2]);
+ if (mag) {
+ y[0] /= mag;
+ y[1] /= mag;
+ y[2] /= mag;
+ }
+
+#define M(row,col) m[col*4+row]
+ M(0, 0) = x[0]; M(0, 1) = x[1]; M(0, 2) = x[2]; M(0, 3) = 0.0;
+ M(1, 0) = y[0]; M(1, 1) = y[1]; M(1, 2) = y[2]; M(1, 3) = 0.0;
+ M(2, 0) = z[0]; M(2, 1) = z[1]; M(2, 2) = z[2]; M(2, 3) = 0.0;
+ M(3, 0) = 0.0; M(3, 1) = 0.0; M(3, 2) = 0.0; M(3, 3) = 1.0;
+#undef M
+
+ jwzgles_glMultMatrixf(m);
+
+ /* Translate Eye to Origin */
+ jwzgles_glTranslatef(-eyex, -eyey, -eyez);
+}
+
+
+static void __gluMultMatrixVecd (const GLdouble matrix[16],
+ const GLdouble in[4],
+ GLdouble out[4])
+{
+ int i;
+
+ for (i=0; i<4; i++) {
+ out[i] =
+ in[0] * matrix[0*4+i] +
+ in[1] * matrix[1*4+i] +
+ in[2] * matrix[2*4+i] +
+ in[3] * matrix[3*4+i];
+ }
+}
+
+GLint
+jwzgles_gluProject (GLdouble objx, GLdouble objy, GLdouble objz,
+ const GLdouble modelMatrix[16],
+ const GLdouble projMatrix[16],
+ const GLint viewport[4],
+ GLdouble *winx, GLdouble *winy, GLdouble *winz)
+{
+ GLdouble in[4];
+ GLdouble out[4];
+
+ /* #### I suspect this is not working right. I was seeing crazy values
+ in lament.c. Maybe there's some float-vs-double confusion going on?
+ */
+
+ in[0]=objx;
+ in[1]=objy;
+ in[2]=objz;
+ in[3]=1.0;
+ __gluMultMatrixVecd(modelMatrix, in, out);
+ __gluMultMatrixVecd(projMatrix, out, in);
+ if (in[3] == 0.0) return(GL_FALSE);
+ in[0] /= in[3];
+ in[1] /= in[3];
+ in[2] /= in[3];
+ /* Map x, y and z to range 0-1 */
+ in[0] = in[0] * 0.5 + 0.5;
+ in[1] = in[1] * 0.5 + 0.5;
+ in[2] = in[2] * 0.5 + 0.5;
+
+ /* Map x,y to viewport */
+ in[0] = in[0] * viewport[2] + viewport[0];
+ in[1] = in[1] * viewport[3] + viewport[1];
+
+ *winx=in[0];
+ *winy=in[1];
+ *winz=in[2];
+ return(GL_TRUE);
+}
+
+
+/* OpenGL ES has different extensions vs. regular OpenGL, but the basic
+ principle for checking for extensions is the same.
+ */
+GLboolean
+jwzgles_gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string)
+{
+ size_t ext_len = strlen ((const char *)ext_name);
+
+ for (;;) {
+ const GLubyte *found = (const GLubyte *)strstr ((const char *)ext_string,
+ (const char *)ext_name);
+ if (!found)
+ break;
+
+ char last_ch = found[ext_len];
+ if ((found == ext_string || found[-1] == ' ') &&
+ (last_ch == ' ' || !last_ch)) {
+ return GL_TRUE;
+ }
+
+ ext_string = found + ext_len;
+ }
+
+ return GL_FALSE;
+}
+
+
+void jwzgles_glViewport (GLuint x, GLuint y, GLuint w, GLuint h)
+{
+# if TARGET_IPHONE_SIMULATOR
+/* fprintf (stderr, "glViewport %dx%d\n", w, h); */
+# endif
+ glViewport (x, y, w, h); /* the real one */
+}
+
+
+/* The following functions are present in both OpenGL 1.1 and in OpenGLES 1,
+ but are allowed within glNewList/glEndList, so we must wrap them to allow
+ them to either be recorded in lists, or run directly.
+
+ All this CPP obscenity is me screaming in rage at all the ways that C is
+ not Lisp, as all I want to do here is DEFADVICE.
+ */
+
+#define PROTO_V PROTO_VOID
+#define TYPE_V GLuint
+#define ARGS_V void
+#define VARS_V /* */
+#define LOGS_V "\n"
+#define FILL_V /* */
+
+#define TYPE_I GLuint
+#define TYPE_II TYPE_I
+#define TYPE_III TYPE_I
+#define TYPE_IIII TYPE_I
+#define ARGS_I TYPE_I a
+#define ARGS_II TYPE_I a, TYPE_I b
+#define ARGS_III TYPE_I a, TYPE_I b, TYPE_I c
+#define ARGS_IIII TYPE_I a, TYPE_I b, TYPE_I c, TYPE_I d
+#define LOGS_I "%s\n", mode_desc(a)
+#define LOGS_II "%s %d\n", mode_desc(a), b
+#define LOGS_III "%s %s %s\n", mode_desc(a), mode_desc(b), mode_desc(c)
+#define LOGS_IIII "%d %d %d %d\n", a, b, c, d
+#define VARS_I a
+#define VARS_II a, b
+#define VARS_III a, b, c
+#define VARS_IIII a, b, c, d
+#define FILL_I vv[0].i = a;
+#define FILL_II vv[0].i = a; vv[1].i = b;
+#define FILL_III vv[0].i = a; vv[1].i = b; vv[2].i = c;
+#define FILL_IIII vv[0].i = a; vv[1].i = b; vv[2].i = c; vv[3].i = d;
+
+#define TYPE_F GLfloat
+#define TYPE_FF TYPE_F
+#define TYPE_FFF TYPE_F
+#define TYPE_FFFF TYPE_F
+#define ARGS_F TYPE_F a
+#define ARGS_FF TYPE_F a, TYPE_F b
+#define ARGS_FFF TYPE_F a, TYPE_F b, TYPE_F c
+#define ARGS_FFFF TYPE_F a, TYPE_F b, TYPE_F c, TYPE_F d
+#define LOGS_F "%7.3f\n", a
+#define LOGS_FF "%7.3f %7.3f\n", a, b
+#define LOGS_FFF "%7.3f %7.3f %7.3f\n", a, b, c
+#define LOGS_FFFF "%7.3f %7.3f %7.3f %7.3f\n", a, b, c, d
+#define VARS_F VARS_I
+#define VARS_FF VARS_II
+#define VARS_FFF VARS_III
+#define VARS_FFFF VARS_IIII
+#define FILL_F vv[0].f = a;
+#define FILL_FF vv[0].f = a; vv[1].f = b;
+#define FILL_FFF vv[0].f = a; vv[1].f = b; vv[2].f = c;
+#define FILL_FFFF vv[0].f = a; vv[1].f = b; vv[2].f = c; vv[3].f = d;
+
+#define ARGS_IF TYPE_I a, TYPE_F b
+#define VARS_IF VARS_II
+#define LOGS_IF "%s %7.3f\n", mode_desc(a), b
+#define FILL_IF vv[0].i = a; vv[1].f = b;
+
+#define ARGS_IIF TYPE_I a, TYPE_I b, TYPE_F c
+#define VARS_IIF VARS_III
+#define LOGS_IIF "%s %s %7.3f\n", mode_desc(a), mode_desc(b), c
+#define FILL_IIF vv[0].i = a; vv[1].i = b; vv[2].f = c;
+
+#define TYPE_IV GLint
+#define ARGS_IIV TYPE_I a, const TYPE_IV *b
+#define VARS_IIV VARS_II
+#define LOGS_IIV "%s %d %d %d %d\n", mode_desc(a), b[0], b[1], b[2], b[3]
+#define FILL_IIV vv[0].i = a; \
+ vv[1].i = b[0]; vv[2].i = b[1]; \
+ vv[3].i = b[2]; vv[4].i = b[3];
+
+#define ARGS_IFV TYPE_I a, const TYPE_F *b
+#define VARS_IFV VARS_II
+#define LOGS_IFV "%s %7.3f %7.3f %7.3f %7.3f\n", mode_desc(a), \
+ b[0], b[1], b[2], b[3]
+#define FILL_IFV vv[0].i = a; \
+ vv[1].f = b[0]; vv[2].f = b[1]; \
+ vv[3].f = b[2]; vv[4].f = b[3];
+
+#define ARGS_IIIV TYPE_I a, TYPE_I b, const TYPE_IV *c
+#define VARS_IIIV VARS_III
+#define LOGS_IIIV "%s %-8s %3d %3d %3d %3d\n", mode_desc(a), mode_desc(b), \
+ c[0], c[1], c[2], c[3]
+#define FILL_IIIV vv[0].i = a; vv[1].i = b; \
+ vv[2].i = c[0]; vv[3].i = c[1]; \
+ vv[4].i = c[2]; vv[5].i = c[3];
+
+#define ARGS_IIFV TYPE_I a, TYPE_I b, const TYPE_F *c
+#define VARS_IIFV VARS_III
+#define LOGS_IIFV "%s %-8s %7.3f %7.3f %7.3f %7.3f\n", \
+ mode_desc(a), mode_desc(b), \
+ c[0], c[1], c[2], c[3]
+#define FILL_IIFV vv[0].i = a; vv[1].i = b; \
+ vv[2].f = c[0]; vv[3].f = c[1]; \
+ vv[4].f = c[2]; vv[5].f = c[3];
+
+#ifdef DEBUG
+# define WLOG(NAME,ARGS) \
+ fprintf (stderr, "jwzgles: direct %-12s ", NAME); \
+ fprintf (stderr, ARGS)
+#else
+# define WLOG(NAME,ARGS) /* */
+#endif
+
+#define WRAP(NAME,SIG) \
+void jwzgles_##NAME (ARGS_##SIG) \
+{ \
+ Assert (!state->compiling_verts, \
+ STRINGIFY(NAME) " not allowed inside glBegin"); \
+ if (state->compiling_list) { \
+ void_int vv[10]; \
+ FILL_##SIG \
+ list_push (STRINGIFY(NAME), (list_fn_cb) &jwzgles_##NAME, \
+ PROTO_##SIG, vv); \
+ } else { \
+ if (! state->replaying_list) { \
+ WLOG (STRINGIFY(NAME), LOGS_##SIG); \
+ } \
+ NAME (VARS_##SIG); \
+ CHECK(STRINGIFY(NAME)); \
+ } \
+}
+
+WRAP (glActiveTexture, I)
+WRAP (glAlphaFunc, IF)
+WRAP (glBlendFunc, II)
+WRAP (glClear, I)
+WRAP (glClearColor, FFFF)
+WRAP (glClearStencil, I)
+WRAP (glColorMask, IIII)
+WRAP (glCullFace, I)
+WRAP (glDepthFunc, I)
+WRAP (glDepthMask, I)
+WRAP (glFinish, V)
+WRAP (glFlush, V)
+WRAP (glFogf, IF)
+WRAP (glFogfv, IFV)
+WRAP (glFrontFace, I)
+WRAP (glHint, II)
+WRAP (glLightModelf, IF)
+WRAP (glLightModelfv, IFV)
+WRAP (glLightf, IIF)
+WRAP (glLightfv, IIFV)
+WRAP (glLineWidth, F)
+WRAP (glLoadIdentity, V)
+WRAP (glLogicOp, I)
+WRAP (glMatrixMode, I)
+WRAP (glPixelStorei, II)
+WRAP (glPointSize, F)
+WRAP (glPolygonOffset, FF)
+WRAP (glPopMatrix, V)
+WRAP (glPushMatrix, V)
+WRAP (glRotatef, FFFF)
+WRAP (glScalef, FFF)
+WRAP (glScissor, IIII)
+WRAP (glShadeModel, I)
+WRAP (glStencilFunc, III)
+WRAP (glStencilMask, I)
+WRAP (glStencilOp, III)
+WRAP (glTexEnvf, IIF)
+WRAP (glTexEnvi, III)
+WRAP (glTranslatef, FFF)
+#undef TYPE_IV
+#define TYPE_IV GLuint
+WRAP (glDeleteTextures, IIV)
+
+
+#endif /* HAVE_JWZGLES - whole file */
diff --git a/jwxyz/jwzgles.h b/jwxyz/jwzgles.h
new file mode 100644
index 0000000..9c8d1e2
--- /dev/null
+++ b/jwxyz/jwzgles.h
@@ -0,0 +1,520 @@
+/* xscreensaver, Copyright (c) 2012 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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* A compatibility shim to allow OpenGL 1.3 source code to work in an
+ OpenGLES environment, where almost every OpenGL 1.3 function has
+ been "deprecated". See jwzgles.c for details.
+ */
+
+#ifndef __JWZGLES_H__
+#define __JWZGLES_H__
+
+#ifndef HAVE_JWZGLES
+# error: do not include this without HAVE_JWZGLES
+#endif
+
+
+#include "jwzglesI.h"
+
+
+/* These are the OpenGL 1.3 functions that are not present in OpenGLES 1.
+ As you can see from the length of this list, OpenGL and OpenGLES have
+ almost nothing to do with each other. To claim that GLES is a dialect
+ of OpenGL is absurd -- English and Latin have more in common!
+ */
+
+#define glAccum jwzgles_glAccum
+#define glAntialiasing jwzgles_glAntialiasing
+#define glAreTexturesResident jwzgles_glAreTexturesResident
+#define glArrayElement jwzgles_glArrayElement
+#define glBegin jwzgles_glBegin
+#define glBitmap jwzgles_glBitmap
+#define glBlendColor jwzgles_glBlendColor
+#define glBlendEquation jwzgles_glBlendEquation
+#define glCallList jwzgles_glCallList
+#define glCallLists jwzgles_glCallLists
+#define glClearAccum jwzgles_glClearAccum
+#define glClearDepth jwzgles_glClearDepth
+#define glClearIndex jwzgles_glClearIndex
+#define glClipPlane jwzgles_glClipPlane
+#define glColor3b jwzgles_glColor3b
+#define glColor3bv jwzgles_glColor3bv
+#define glColor3d jwzgles_glColor3f
+#define glColor3dv jwzgles_glColor3dv
+#define glColor3f jwzgles_glColor3f
+#define glColor3fv jwzgles_glColor3fv
+#define glColor3i jwzgles_glColor3i
+#define glColor3iv jwzgles_glColor3iv
+#define glColor3s jwzgles_glColor3s
+#define glColor3sv jwzgles_glColor3sv
+#define glColor3ub jwzgles_glColor3ub
+#define glColor3ubv jwzgles_glColor3ubv
+#define glColor3ui jwzgles_glColor3ui
+#define glColor3uiv jwzgles_glColor3uiv
+#define glColor3us jwzgles_glColor3us
+#define glColor3usv jwzgles_glColor3usv
+#define glColor4b jwzgles_glColor4b
+#define glColor4bv jwzgles_glColor4bv
+#define glColor4d jwzgles_glColor4d
+#define glColor4dv jwzgles_glColor4dv
+#define glColor4fv jwzgles_glColor4fv
+#define glColor4i jwzgles_glColor4i
+#define glColor4iv jwzgles_glColor4iv
+#define glColor4s jwzgles_glColor4s
+#define glColor4sv jwzgles_glColor4sv
+#define glColor4ub jwzgles_glColor4ub
+#define glColor4ubv jwzgles_glColor4ubv
+#define glColor4ui jwzgles_glColor4ui
+#define glColor4uiv jwzgles_glColor4uiv
+#define glColor4us jwzgles_glColor4us
+#define glColor4usv jwzgles_glColor4usv
+#define glColorMaterial jwzgles_glColorMaterial
+#define glColorSubTable jwzgles_glColorSubTable
+#define glColorTable jwzgles_glColorTable
+#define glColorTableParameter jwzgles_glColorTableParameter
+#define glColorTableParameterfv jwzgles_glColorTableParameterfv
+#define glColorub jwzgles_glColorub
+#define glColorui jwzgles_glColorui
+#define glColorus jwzgles_glColorus
+#define glCompressedTexImage jwzgles_glCompressedTexImage
+#define glCompressedTexImage1D jwzgles_glCompressedTexImage1D
+#define glCompressedTexImage3D jwzgles_glCompressedTexImage3D
+#define glCompressedTexSubImage1D jwzgles_glCompressedTexSubImage1D
+#define glCompressedTexSubImage3D jwzgles_glCompressedTexSubImage3D
+#define glConvolutionFilter1D jwzgles_glConvolutionFilter1D
+#define glConvolutionFilter2D jwzgles_glConvolutionFilter2D
+#define glConvolutionParameter jwzgles_glConvolutionParameter
+#define glConvolutionParameterfv jwzgles_glConvolutionParameterfv
+#define glConvolutionParameteriv jwzgles_glConvolutionParameteriv
+#define glCopyColorSubTable jwzgles_glCopyColorSubTable
+#define glCopyColorTable jwzgles_glCopyColorTable
+#define glCopyConvolutionFilter1D jwzgles_glCopyConvolutionFilter1D
+#define glCopyConvolutionFilter2D jwzgles_glCopyConvolutionFilter2D
+#define glCopyPixels jwzgles_glCopyPixels
+#define glCopyTexImage1D jwzgles_glCopyTexImage1D
+#define glCopyTexImage3D jwzgles_glCopyTexImage3D
+#define glCopyTexSubImage1D jwzgles_glCopyTexSubImage1D
+#define glCopyTexSubImage3D jwzgles_glCopyTexSubImage3D
+#define glDeleteLists jwzgles_glDeleteLists
+#define glDepthRange jwzgles_glDepthRange
+#define glDrawBuffer jwzgles_glDrawBuffer
+#define glDrawPixels jwzgles_glDrawPixels
+#define glDrawRangeElements jwzgles_glDrawRangeElements
+#define glEdgeFlag jwzgles_glEdgeFlag
+#define glEdgeFlagPointer jwzgles_glEdgeFlagPointer
+#define glEdgeFlagv jwzgles_glEdgeFlagv
+#define glEnd jwzgles_glEnd
+#define glEndList jwzgles_glEndList
+#define glEvalCoord1d jwzgles_glEvalCoord1d
+#define glEvalCoord1dv jwzgles_glEvalCoord1dv
+#define glEvalCoord1f jwzgles_glEvalCoord1f
+#define glEvalCoord1fv jwzgles_glEvalCoord1fv
+#define glEvalCoord2d jwzgles_glEvalCoord2d
+#define glEvalCoord2dv jwzgles_glEvalCoord2dv
+#define glEvalCoord2f jwzgles_glEvalCoord2f
+#define glEvalCoord2fv jwzgles_glEvalCoord2fv
+#define glEvalMesh1 jwzgles_glEvalMesh1
+#define glEvalMesh2 jwzgles_glEvalMesh2
+#define glEvalPoint1 jwzgles_glEvalPoint1
+#define glEvalPoint2 jwzgles_glEvalPoint2
+#define glFeedbackBuffer jwzgles_glFeedbackBuffer
+#define glFogi jwzgles_glFogi
+#define glFogiv jwzgles_glFogiv
+#define glFrustum jwzgles_glFrustum
+#define glGenLists jwzgles_glGenLists
+#define glGet jwzgles_glGet
+#define glGetBooleanv jwzgles_glGetBooleanv
+#define glGetClipPlane jwzgles_glGetClipPlane
+#define glGetColorTable jwzgles_glGetColorTable
+#define glGetColorTableParameter jwzgles_glGetColorTableParameter
+#define glGetCompressedTexImage jwzgles_glGetCompressedTexImage
+#define glGetConvolutionFilter jwzgles_glGetConvolutionFilter
+#define glGetConvolutionParameter jwzgles_glGetConvolutionParameter
+#define glGetConvolutionParameteriv jwzgles_glGetConvolutionParameteriv
+#define glGetDoublev jwzgles_glGetDoublev
+#define glGetFloatv jwzgles_glGetFloatv
+#define glGetHistogram jwzgles_glGetHistogram
+#define glGetHistogramParameter jwzgles_glGetHistogramParameter
+#define glGetLightfv jwzgles_glGetLightfv
+#define glGetLightiv jwzgles_glGetLightiv
+#define glGetMapdv jwzgles_glGetMapdv
+#define glGetMapfv jwzgles_glGetMapfv
+#define glGetMapiv jwzgles_glGetMapiv
+#define glGetMaterialfv jwzgles_glGetMaterialfv
+#define glGetMaterialiv jwzgles_glGetMaterialiv
+#define glGetPixelMapfv jwzgles_glGetPixelMapfv
+#define glGetPixelMapuiv jwzgles_glGetPixelMapuiv
+#define glGetPixelMapusv jwzgles_glGetPixelMapusv
+#define glGetPointerv jwzgles_glGetPointerv
+#define glGetPolygonStipple jwzgles_glGetPolygonStipple
+#define glGetSeparableFilter jwzgles_glGetSeparableFilter
+#define glGetTexEnvfv jwzgles_glGetTexEnvfv
+#define glGetTexEnviv jwzgles_glGetTexEnviv
+#define glGetTexGendv jwzgles_glGetTexGendv
+#define glGetTexGenfv jwzgles_glGetTexGenfv
+#define glGetTexGeniv jwzgles_glGetTexGeniv
+#define glGetTexImage jwzgles_glGetTexImage
+#define glGetTexImage1D jwzgles_glGetTexImage1D
+#define glGetTexImage2D jwzgles_glGetTexImage2D
+#define glGetTexImage3D jwzgles_glGetTexImage3D
+#define glGetTexLevelParameterfv jwzgles_glGetTexLevelParameterfv
+#define glGetTexLevelParameteriv jwzgles_glGetTexLevelParameteriv
+#define glGetTexParameterfv jwzgles_glGetTexParameterfv
+#define glGetTexParameteriv jwzgles_glGetTexParameteriv
+#define glHistogram jwzgles_glHistogram
+#define glIndex jwzgles_glIndex
+#define glIndexMask jwzgles_glIndexMask
+#define glIndexPointer jwzgles_glIndexPointer
+#define glIndexd jwzgles_glIndexd
+#define glIndexdv jwzgles_glIndexdv
+#define glIndexf jwzgles_glIndexf
+#define glIndexfv jwzgles_glIndexfv
+/*#define glIndexi jwzgles_glIndexi*/
+#define glIndexiv jwzgles_glIndexiv
+#define glIndexs jwzgles_glIndexs
+#define glIndexsv jwzgles_glIndexsv
+#define glIndexub jwzgles_glIndexub
+#define glIndexubv jwzgles_glIndexubv
+#define glInitNames jwzgles_glInitNames
+#define glInterleavedArrays jwzgles_glInterleavedArrays
+#define glIsEnabled jwzgles_glIsEnabled
+#define glIsList jwzgles_glIsList
+#define glIsTexture jwzgles_glIsTexture
+#define glLightModeli jwzgles_glLightModeli
+#define glLightModeliv jwzgles_glLightModeliv
+#define glLighti jwzgles_glLighti
+#define glLightiv jwzgles_glLightiv
+#define glLightf jwzgles_glLightf
+#define glLightfv jwzgles_glLightfv
+#define glLineStipple jwzgles_glLineStipple
+#define glListBase jwzgles_glListBase
+#define glLoadMatrix jwzgles_glLoadMatrix
+#define glLoadMatrixd jwzgles_glLoadMatrixd
+#define glLoadName jwzgles_glLoadName
+#define glLoadTransposeMatrix jwzgles_glLoadTransposeMatrix
+#define glLoadTransposeMatrixd jwzgles_glLoadTransposeMatrixd
+#define glLoadTransposeMatrixf jwzgles_glLoadTransposeMatrixf
+#define glMap1d jwzgles_glMap1d
+#define glMap1f jwzgles_glMap1f
+#define glMap2d jwzgles_glMap2d
+#define glMap2f jwzgles_glMap2f
+#define glMapGrid1d jwzgles_glMapGrid1d
+#define glMapGrid1f jwzgles_glMapGrid1f
+#define glMapGrid2d jwzgles_glMapGrid2d
+#define glMapGrid2f jwzgles_glMapGrid2f
+#define glMateriali jwzgles_glMateriali
+#define glMaterialiv jwzgles_glMaterialiv
+#define glMultMatrixd jwzgles_glMultMatrixd
+#define glMultTransposeMatrix jwzgles_glMultTransposeMatrix
+#define glMultTransposeMatrixd jwzgles_glMultTransposeMatrixd
+#define glMultTransposeMatrixf jwzgles_glMultTransposeMatrixf
+#define glMultiTexCoord jwzgles_glMultiTexCoord
+#define glNewList jwzgles_glNewList
+#define glNormal3b jwzgles_glNormal3b
+#define glNormal3bv jwzgles_glNormal3bv
+#define glNormal3d jwzgles_glNormal3f
+#define glNormal3dv jwzgles_glNormal3dv
+#define glNormal3fv jwzgles_glNormal3fv
+#define glNormal3i jwzgles_glNormal3i
+#define glNormal3iv jwzgles_glNormal3iv
+#define glNormal3s jwzgles_glNormal3s
+#define glNormal3sv jwzgles_glNormal3sv
+#define glOrtho jwzgles_glOrtho
+#define glPassThrough jwzgles_glPassThrough
+#define glPixelMapfv jwzgles_glPixelMapfv
+#define glPixelMapuiv jwzgles_glPixelMapuiv
+#define glPixelMapusv jwzgles_glPixelMapusv
+#define glPixelStoref jwzgles_glPixelStoref
+#define glPixelTransferf jwzgles_glPixelTransferf
+#define glPixelTransferi jwzgles_glPixelTransferi
+#define glPixelZoom jwzgles_glPixelZoom
+#define glPolygonMode jwzgles_glPolygonMode
+#define glPolygonStipple jwzgles_glPolygonStipple
+#define glPopAttrib jwzgles_glPopAttrib
+#define glPopClientAttrib jwzgles_glPopClientAttrib
+#define glPopName jwzgles_glPopName
+#define glPrioritizeTextures jwzgles_glPrioritizeTextures
+#define glPushAttrib jwzgles_glPushAttrib
+#define glPushClientAttrib jwzgles_glPushClientAttrib
+#define glPushName jwzgles_glPushName
+#define glRasterPos2d jwzgles_glRasterPos2d
+#define glRasterPos2dv jwzgles_glRasterPos2dv
+#define glRasterPos2f jwzgles_glRasterPos2f
+#define glRasterPos2fv jwzgles_glRasterPos2fv
+#define glRasterPos2i jwzgles_glRasterPos2i
+#define glRasterPos2iv jwzgles_glRasterPos2iv
+#define glRasterPos2s jwzgles_glRasterPos2s
+#define glRasterPos2sv jwzgles_glRasterPos2sv
+#define glRasterPos3d jwzgles_glRasterPos3d
+#define glRasterPos3dv jwzgles_glRasterPos3dv
+#define glRasterPos3f jwzgles_glRasterPos3f
+#define glRasterPos3fv jwzgles_glRasterPos3fv
+#define glRasterPos3i jwzgles_glRasterPos3i
+#define glRasterPos3iv jwzgles_glRasterPos3iv
+#define glRasterPos3s jwzgles_glRasterPos3s
+#define glRasterPos3sv jwzgles_glRasterPos3sv
+#define glRasterPos4d jwzgles_glRasterPos4d
+#define glRasterPos4dv jwzgles_glRasterPos4dv
+#define glRasterPos4f jwzgles_glRasterPos4f
+#define glRasterPos4fv jwzgles_glRasterPos4fv
+#define glRasterPos4i jwzgles_glRasterPos4i
+#define glRasterPos4iv jwzgles_glRasterPos4iv
+#define glRasterPos4s jwzgles_glRasterPos4s
+#define glRasterPos4sv jwzgles_glRasterPos4sv
+#define glReadBuffer jwzgles_glReadBuffer
+#define glRectd jwzgles_glRectf
+#define glRectdv jwzgles_glRectdv
+#define glRectf jwzgles_glRectf
+#define glRectfv jwzgles_glRectfv
+#define glRecti jwzgles_glRecti
+#define glRectiv jwzgles_glRectiv
+#define glRects jwzgles_glRects
+#define glRectsv jwzgles_glRectsv
+#define glRenderMode jwzgles_glRenderMode
+#define glResetHistogram jwzgles_glResetHistogram
+#define glResetMinmax jwzgles_glResetMinmax
+#define glRotated jwzgles_glRotated
+#define glScaled jwzgles_glScalef
+#define glSelectBuffer jwzgles_glSelectBuffer
+#define glSeparableFilter2D jwzgles_glSeparableFilter2D
+#define glTexCoord1d jwzgles_glTexCoord1d
+#define glTexCoord1dv jwzgles_glTexCoord1dv
+#define glTexCoord1f jwzgles_glTexCoord1f
+#define glTexCoord1fv jwzgles_glTexCoord1fv
+#define glTexCoord1i jwzgles_glTexCoord1i
+#define glTexCoord1iv jwzgles_glTexCoord1iv
+#define glTexCoord1s jwzgles_glTexCoord1s
+#define glTexCoord1sv jwzgles_glTexCoord1sv
+#define glTexCoord2d jwzgles_glTexCoord2f
+#define glTexCoord2dv jwzgles_glTexCoord2dv
+#define glTexCoord2f jwzgles_glTexCoord2f
+#define glTexCoord2fv jwzgles_glTexCoord2fv
+#define glTexCoord2i jwzgles_glTexCoord2i
+#define glTexCoord2iv jwzgles_glTexCoord2iv
+#define glTexCoord2s jwzgles_glTexCoord2s
+#define glTexCoord2sv jwzgles_glTexCoord2sv
+#define glTexCoord3d jwzgles_glTexCoord3d
+#define glTexCoord3dv jwzgles_glTexCoord3dv
+#define glTexCoord3f jwzgles_glTexCoord3f
+#define glTexCoord3fv jwzgles_glTexCoord3fv
+#define glTexCoord3i jwzgles_glTexCoord3i
+#define glTexCoord3iv jwzgles_glTexCoord3iv
+#define glTexCoord3s jwzgles_glTexCoord3s
+#define glTexCoord3sv jwzgles_glTexCoord3sv
+#define glTexCoord4d jwzgles_glTexCoord4d
+#define glTexCoord4dv jwzgles_glTexCoord4dv
+#define glTexCoord4f jwzgles_glTexCoord4f
+#define glTexCoord4fv jwzgles_glTexCoord4fv
+#define glTexCoord4i jwzgles_glTexCoord4i
+#define glTexCoord4iv jwzgles_glTexCoord4iv
+#define glTexCoord4s jwzgles_glTexCoord4s
+#define glTexCoord4sv jwzgles_glTexCoord4sv
+#define glTexEnvi jwzgles_glTexEnvi
+#define glTexEnviv jwzgles_glTexEnviv
+#define glTexGend jwzgles_glTexGend
+#define glTexGendv jwzgles_glTexGendv
+#define glTexGenf jwzgles_glTexGenf
+#define glTexGenfv jwzgles_glTexGenfv
+#define glTexGeni jwzgles_glTexGeni
+#define glTexGeniv jwzgles_glTexGeniv
+#define glTexImage1D jwzgles_glTexImage1D
+#define glTexImage3D jwzgles_glTexImage3D
+#define glTexParameterfv jwzgles_glTexParameterfv
+#define glTexParameteri jwzgles_glTexParameteri
+#define glTexParameteriv jwzgles_glTexParameteriv
+#define glTexSubImage1D jwzgles_glTexSubImage1D
+#define glTexSubImage3D jwzgles_glTexSubImage3D
+#define glTranslated jwzgles_glTranslatef
+#define glVertex2d jwzgles_glVertex2d
+#define glVertex2dv jwzgles_glVertex2dv
+#define glVertex2f jwzgles_glVertex2f
+#define glVertex2fv jwzgles_glVertex2fv
+#define glVertex2i jwzgles_glVertex2i
+#define glVertex2iv jwzgles_glVertex2iv
+#define glVertex2s jwzgles_glVertex2s
+#define glVertex2sv jwzgles_glVertex2sv
+#define glVertex3d jwzgles_glVertex3f
+#define glVertex3dv jwzgles_glVertex3dv
+#define glVertex3f jwzgles_glVertex3f
+#define glVertex3fv jwzgles_glVertex3fv
+#define glVertex3i jwzgles_glVertex3i
+#define glVertex3iv jwzgles_glVertex3iv
+#define glVertex3s jwzgles_glVertex3s
+#define glVertex3sv jwzgles_glVertex3sv
+#define glVertex4d jwzgles_glVertex4d
+#define glVertex4dv jwzgles_glVertex4dv
+#define glVertex4f jwzgles_glVertex4f
+#define glVertex4fv jwzgles_glVertex4fv
+#define glVertex4i jwzgles_glVertex4i
+#define glVertex4iv jwzgles_glVertex4iv
+#define glVertex4s jwzgles_glVertex4s
+#define glVertex4sv jwzgles_glVertex4sv
+
+#define gluOrtho2D(L,R,B,T) glOrtho(L,R,B,T,-1,1)
+#define gluPerspective jwzgles_gluPerspective
+
+#define glXChooseVisual jwzgles_glXChooseVisual
+#define glXCopyContext jwzgles_glXCopyContext
+/*#define glXCreateContext jwzgles_glXCreateContext*/
+#define glXCreateGLXPixmap jwzgles_glXCreateGLXPixmap
+#define glXDestroyContext jwzgles_glXDestroyContext
+#define glXDestroyGLXPixmap jwzgles_glXDestroyGLXPixmap
+#define glXFreeContextEXT jwzgles_glXFreeContextEXT
+#define glXGetClientString jwzgles_glXGetClientString
+#define glXGetConfig jwzgles_glXGetConfig
+#define glXGetContextIDEXT jwzgles_glXGetContextIDEXT
+#define glXGetCurrentContext jwzgles_glXGetCurrentContext
+#define glXGetCurrentDisplay jwzgles_glXGetCurrentDisplay
+#define glXGetCurrentDrawable jwzgles_glXGetCurrentDrawable
+#define glXImportContextEXT jwzgles_glXImportContextEXT
+#define glXIntro jwzgles_glXIntro
+#define glXIsDirect jwzgles_glXIsDirect
+/*#define glXMakeCurrent jwzgles_glXMakeCurrent*/
+#define glXQueryContextInfoEXT jwzgles_glXQueryContextInfoEXT
+#define glXQueryExtension jwzgles_glXQueryExtension
+#define glXQueryExtensionsString jwzgles_glXQueryExtensionsString
+#define glXQueryServerString jwzgles_glXQueryServerString
+#define glXQueryVersion jwzgles_glXQueryVersion
+/*#define glXSwapBuffers jwzgles_glXSwapBuffers*/
+#define glXUseXFont jwzgles_glXUseXFont
+#define glXWaitGL jwzgles_glXWaitGL
+#define glXWaitX jwzgles_glXWaitX
+
+#define gluBeginCurve jwzgles_gluBeginCurve
+#define gluBeginPolygon jwzgles_gluBeginPolygon
+#define gluBeginSurface jwzgles_gluBeginSurface
+#define gluBeginTrim jwzgles_gluBeginTrim
+#define gluBuild1DMipmaps jwzgles_gluBuild1DMipmaps
+#define gluBuild2DMipmaps jwzgles_gluBuild2DMipmaps
+#define gluCheckExtension jwzgles_gluCheckExtension
+#define gluCylinder jwzgles_gluCylinder
+#define gluDeleteNurbsRenderer jwzgles_gluDeleteNurbsRenderer
+#define gluDeleteQuadric jwzgles_gluDeleteQuadric
+#define gluDeleteTess jwzgles_gluDeleteTess
+#define gluDisk jwzgles_gluDisk
+#define gluEndCurve jwzgles_gluEndCurve
+#define gluEndPolygon jwzgles_gluEndPolygon
+#define gluEndSurface jwzgles_gluEndSurface
+#define gluEndTrim jwzgles_gluEndTrim
+#define gluErrorString jwzgles_gluErrorString
+#define gluGetNurbsProperty jwzgles_gluGetNurbsProperty
+#define gluGetString jwzgles_gluGetString
+#define gluGetTessProperty jwzgles_gluGetTessProperty
+#define gluLoadSamplingMatrices jwzgles_gluLoadSamplingMatrices
+#define gluLookAt jwzgles_gluLookAt
+#define gluNewNurbsRenderer jwzgles_gluNewNurbsRenderer
+#define gluNewQuadric jwzgles_gluNewQuadric
+#define gluNewTess jwzgles_gluNewTess
+#define gluNextContour jwzgles_gluNextContour
+#define gluNurbsCallback jwzgles_gluNurbsCallback
+#define gluNurbsCurve jwzgles_gluNurbsCurve
+#define gluNurbsProperty jwzgles_gluNurbsProperty
+#define gluNurbsSurface jwzgles_gluNurbsSurface
+#define gluPartialDisk jwzgles_gluPartialDisk
+#define gluPickMatrix jwzgles_gluPickMatrix
+#define gluProject jwzgles_gluProject
+#define gluPwlCurve jwzgles_gluPwlCurve
+#define gluQuadricCallback jwzgles_gluQuadricCallback
+#define gluQuadricDrawStyle jwzgles_gluQuadricDrawStyle
+#define gluQuadricNormals jwzgles_gluQuadricNormals
+#define gluQuadricOrientation jwzgles_gluQuadricOrientation
+#define gluQuadricTexture jwzgles_gluQuadricTexture
+#define gluScaleImage jwzgles_gluScaleImage
+#define gluSphere jwzgles_gluSphere
+#define gluTessBeginContour jwzgles_gluTessBeginContour
+#define gluTessBeginPolygon jwzgles_gluTessBeginPolygon
+#define gluTessCallback jwzgles_gluTessCallback
+#define gluTessEndPolygon jwzgles_gluTessEndPolygon
+#define gluTessEndContour jwzgles_gluTessEndContour
+#define gluTessNormal jwzgles_gluTessNormal
+#define gluTessProperty jwzgles_gluTessProperty
+#define gluTessVertex jwzgles_gluTessVertex
+#define gluUnProject jwzgles_gluUnProject
+
+
+/* These functions are present in both OpenGL 1.1 and in OpenGLES 1,
+ but are allowed within glNewList/glEndList, so we must wrap them
+ to allow them to be recorded.
+ */
+#define glActiveTexture jwzgles_glActiveTexture
+#define glAlphaFunc jwzgles_glAlphaFunc
+#define glBindTexture jwzgles_glBindTexture
+#define glBlendFunc jwzgles_glBlendFunc
+#define glClear jwzgles_glClear
+#define glClearColor jwzgles_glClearColor
+#define glClearStencil jwzgles_glClearStencil
+#define glColor4f jwzgles_glColor4f
+#define glColorMask jwzgles_glColorMask
+#define glColorPointer jwzgles_glColorPointer
+#define glCompressedTexImage2D jwzgles_glCompressedTexImage2D
+#define glCompressedTexSubImage2D jwzgles_glCompressedTexSubImage2D
+#define glCopyTexImage2D jwzgles_glCopyTexImage2D
+#define glCopyTexSubImage2D jwzgles_glCopyTexSubImage2D
+#define glCullFace jwzgles_glCullFace
+#define glDeleteTextures jwzgles_glDeleteTextures
+#define glDepthFunc jwzgles_glDepthFunc
+#define glDepthMask jwzgles_glDepthMask
+#define glDisable jwzgles_glDisable
+#define glDrawArrays jwzgles_glDrawArrays
+#define glDrawElements jwzgles_glDrawElements
+#define glEnable jwzgles_glEnable
+#define glFinish jwzgles_glFinish
+#define glFlush jwzgles_glFlush
+#define glFogf jwzgles_glFogf
+#define glFogfv jwzgles_glFogfv
+#define glFrontFace jwzgles_glFrontFace
+#define glGenTextures jwzgles_glGenTextures
+#define glGetIntegerv jwzgles_glGetIntegerv
+#define glHint jwzgles_glHint
+#define glLightModelf jwzgles_glLightModelf
+#define glLightModelfv jwzgles_glLightModelfv
+#define glLightf jwzgles_glLightf
+#define glLightfv jwzgles_glLightfv
+#define glLineWidth jwzgles_glLineWidth
+#define glLoadIdentity jwzgles_glLoadIdentity
+#define glLoadMatrixf jwzgles_glLoadMatrixf
+#define glLogicOp jwzgles_glLogicOp
+#define glMaterialf jwzgles_glMaterialf
+#define glMateriali jwzgles_glMateriali
+#define glMaterialfv jwzgles_glMaterialfv
+#define glMaterialiv jwzgles_glMaterialiv
+#define glMatrixMode jwzgles_glMatrixMode
+#define glMultMatrixf jwzgles_glMultMatrixf
+#define glNormal3f jwzgles_glNormal3f
+#define glNormalPointer jwzgles_glNormalPointer
+#define glPixelStorei jwzgles_glPixelStorei
+#define glPointSize jwzgles_glPointSize
+#define glPolygonOffset jwzgles_glPolygonOffset
+#define glPopMatrix jwzgles_glPopMatrix
+#define glPushMatrix jwzgles_glPushMatrix
+#define glReadPixels jwzgles_glReadPixels
+#define glRotatef jwzgles_glRotatef
+#define glScalef jwzgles_glScalef
+#define glSampleCoverage jwzgles_glSampleCoverage
+#define glScissor jwzgles_glScissor
+#define glShadeModel jwzgles_glShadeModel
+#define glStencilFunc jwzgles_glStencilFunc
+#define glStencilMask jwzgles_glStencilMask
+#define glStencilOp jwzgles_glStencilOp
+#define glTexCoordPointer jwzgles_glTexCoordPointer
+#define glTexEnvf jwzgles_glTexEnvf
+#define glTexEnvfv jwzgles_glTexEnvfv
+#define glTexImage2D jwzgles_glTexImage2D
+#define glTexParameterf jwzgles_glTexParameterf
+#define glTexSubImage2D jwzgles_glTexSubImage2D
+#define glTranslatef jwzgles_glTranslatef
+#define glVertexPointer jwzgles_glVertexPointer
+#define glViewport jwzgles_glViewport
+#define glEnableClientState jwzgles_glEnableClientState
+#define glDisableClientState jwzgles_glDisableClientState
+#define glClipPlane jwzgles_glClipPlane
+
+#endif /* __JWZGLES_H__ */
diff --git a/jwxyz/jwzglesI.h b/jwxyz/jwzglesI.h
new file mode 100644
index 0000000..8f09208
--- /dev/null
+++ b/jwxyz/jwzglesI.h
@@ -0,0 +1,355 @@
+/* xscreensaver, Copyright (c) 2012-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
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* A compatibility shim to allow OpenGL 1.3 source code to work in an
+ OpenGLES environment, where almost every OpenGL 1.3 function has
+ been "deprecated". See jwzgles.c for details.
+ */
+
+#ifndef __JWZGLES_I_H__
+#define __JWZGLES_I_H__
+
+#ifdef GL_VERSION_ES_CM_1_0 /* compiling against OpenGLES 1.x */
+
+/* These OpenGL 1.3 constants are not present in OpenGLES 1.
+ Fortunately, it looks like they didn't re-use any of the numbers,
+ so we can just keep using the OpenGL 1.3 values. I'm actually
+ kind of shocked that the GLES folks passed up that opportunity
+ for further clusterfuckery.
+ */
+# define GLdouble double
+
+# define GL_ACCUM_BUFFER_BIT 0x00000200
+# define GL_ALL_ATTRIB_BITS 0x000FFFFF
+# define GL_AUTO_NORMAL 0x0D80
+# define GL_BLEND_SRC_ALPHA 0x80CB
+# define GL_C3F_V3F 0x2A24
+# define GL_C4F_N3F_V3F 0x2A26
+# define GL_C4UB_V2F 0x2A22
+# define GL_C4UB_V3F 0x2A23
+# define GL_CLAMP 0x2900
+# define GL_COLOR_BUFFER_BIT 0x00004000
+# define GL_COLOR_MATERIAL_FACE 0x0B55
+# define GL_COLOR_MATERIAL_PARAMETER 0x0B56
+# define GL_COMPILE 0x1300
+# define GL_CURRENT_BIT 0x00000001
+# define GL_DEPTH_BUFFER_BIT 0x00000100
+# define GL_DOUBLEBUFFER 0x0C32
+# define GL_ENABLE_BIT 0x00002000
+# define GL_EVAL_BIT 0x00010000
+# define GL_EYE_LINEAR 0x2400
+# define GL_EYE_PLANE 0x2502
+# define GL_FEEDBACK 0x1C01
+# define GL_FILL 0x1B02
+# define GL_FOG_BIT 0x00000080
+# define GL_HINT_BIT 0x00008000
+# define GL_INTENSITY 0x8049
+# define GL_LIGHTING_BIT 0x00000040
+# define GL_LIGHT_MODEL_COLOR_CONTROL 0x81F8
+# define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51
+# define GL_LINE 0x1B01
+# define GL_LINE_BIT 0x00000004
+# define GL_LIST_BIT 0x00020000
+# define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
+# define GL_N3F_V3F 0x2A25
+# define GL_OBJECT_LINEAR 0x2401
+# define GL_OBJECT_PLANE 0x2501
+# define GL_PIXEL_MODE_BIT 0x00000020
+# define GL_POINT_BIT 0x00000002
+# define GL_POLYGON 0x0009
+# define GL_POLYGON_BIT 0x00000008
+# define GL_POLYGON_MODE 0x0B40
+# define GL_POLYGON_SMOOTH 0x0B41
+# define GL_POLYGON_STIPPLE 0x0B42
+# define GL_POLYGON_STIPPLE_BIT 0x00000010
+# define GL_Q 0x2003
+# define GL_QUADS 0x0007
+# define GL_QUAD_STRIP 0x0008
+# define GL_R 0x2002
+# define GL_RENDER 0x1C00
+# define GL_RGBA_MODE 0x0C31
+# define GL_S 0x2000
+# define GL_SCISSOR_BIT 0x00080000
+# define GL_SELECT 0x1C02
+# define GL_SEPARATE_SPECULAR_COLOR 0x81FA
+# define GL_SINGLE_COLOR 0x81F9
+# define GL_SPHERE_MAP 0x2402
+# define GL_STENCIL_BUFFER_BIT 0x00000400
+# define GL_T 0x2001
+# define GL_T2F_C3F_V3F 0x2A2A
+# define GL_T2F_C4F_N3F_V3F 0x2A2C
+# define GL_T2F_C4UB_V3F 0x2A29
+# define GL_T2F_N3F_V3F 0x2A2B
+# define GL_T2F_V3F 0x2A27
+# define GL_T4F_C4F_N3F_V4F 0x2A2D
+# define GL_T4F_V4F 0x2A28
+# define GL_TEXTURE_1D 0x0DE0
+# define GL_TEXTURE_ALPHA_SIZE 0x805F
+# define GL_TEXTURE_BIT 0x00040000
+# define GL_TEXTURE_BLUE_SIZE 0x805E
+# define GL_TEXTURE_BORDER 0x1005
+# define GL_TEXTURE_BORDER_COLOR 0x1004
+# define GL_TEXTURE_COMPONENTS 0x1003
+# define GL_TEXTURE_GEN_MODE 0x2500
+# define GL_TEXTURE_GEN_Q 0x0C63
+# define GL_TEXTURE_GEN_R 0x0C62
+# define GL_TEXTURE_GEN_S 0x0C60
+# define GL_TEXTURE_GEN_T 0x0C61
+# define GL_TEXTURE_GREEN_SIZE 0x805D
+# define GL_TEXTURE_HEIGHT 0x1001
+# define GL_TEXTURE_INTENSITY_SIZE 0x8061
+# define GL_TEXTURE_LUMINANCE_SIZE 0x8060
+# define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
+# define GL_TEXTURE_RED_SIZE 0x805C
+# define GL_TEXTURE_WIDTH 0x1000
+# define GL_TRANSFORM_BIT 0x00001000
+# define GL_UNPACK_ROW_LENGTH 0x0CF2
+# define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
+# define GL_V2F 0x2A20
+# define GL_V3F 0x2A21
+# define GL_VIEWPORT_BIT 0x00000800
+# define GL_INT 0x1404
+# define GL_DOUBLE 0x140A
+
+#endif
+
+
+typedef struct jwzgles_state jwzgles_state;
+
+extern jwzgles_state *jwzgles_make_state (void);
+extern void jwzgles_free_state (void);
+extern void jwzgles_make_current (jwzgles_state *);
+
+
+/* Prototypes for the things re-implemented in jwzgles.c
+ */
+
+extern int jwzgles_glGenLists (int n);
+extern void jwzgles_glNewList (int id, int mode);
+extern void jwzgles_glEndList (void);
+extern void jwzgles_glDeleteLists (int list, int range);
+extern void jwzgles_glBegin (int mode);
+extern void jwzgles_glNormal3fv (const GLfloat *);
+extern void jwzgles_glNormal3f (GLfloat x, GLfloat y, GLfloat z);
+extern void jwzgles_glTexCoord1f (GLfloat s);
+extern void jwzgles_glTexCoord2fv (const GLfloat *);
+extern void jwzgles_glTexCoord2f (GLfloat s, GLfloat t);
+extern void jwzgles_glTexCoord3fv (const GLfloat *);
+extern void jwzgles_glTexCoord3f (GLfloat s, GLfloat t, GLfloat r);
+extern void jwzgles_glTexCoord4fv (const GLfloat *);
+extern void jwzgles_glTexCoord4f (GLfloat s, GLfloat t, GLfloat r, GLfloat q);
+extern void jwzgles_glVertex2f (GLfloat x, GLfloat y);
+extern void jwzgles_glVertex2dv (const GLdouble *);
+extern void jwzgles_glVertex2fv (const GLfloat *);
+extern void jwzgles_glVertex2i (GLint x, GLint y);
+extern void jwzgles_glVertex3f (GLfloat x, GLfloat y, GLfloat z);
+extern void jwzgles_glVertex3dv (const GLdouble *);
+extern void jwzgles_glVertex3fv (const GLfloat *);
+extern void jwzgles_glVertex3i (GLint x, GLint y, GLint z);
+extern void jwzgles_glVertex4f (GLfloat x, GLfloat y, GLfloat z, GLfloat w);
+extern void jwzgles_glVertex4fv (const GLfloat *);
+extern void jwzgles_glVertex4i (GLint x, GLint y, GLint z, GLint w);
+extern void jwzgles_glEnd (void);
+extern void jwzgles_glCallList (int id);
+extern void jwzgles_glClearIndex(GLfloat c);
+extern void jwzgles_glBitmap (GLsizei, GLsizei, GLfloat, GLfloat, GLfloat,
+ GLfloat, const GLubyte *);
+extern void jwzgles_glPushAttrib(int);
+extern void jwzgles_glPopAttrib(void);
+
+
+/* These functions are present in both OpenGL 1.3 and in OpenGLES 1,
+ but are allowed within glNewList/glEndList, so we must wrap them
+ to allow them to be recorded.
+ */
+extern void jwzgles_glActiveTexture (GLuint);
+extern void jwzgles_glBindTexture (GLuint, GLuint);
+extern void jwzgles_glBlendFunc (GLuint, GLuint);
+extern void jwzgles_glClear (GLuint);
+extern void jwzgles_glClearColor (GLclampf, GLclampf, GLclampf, GLclampf);
+extern void jwzgles_glClearStencil (GLuint);
+extern void jwzgles_glColorMask (GLuint, GLuint, GLuint, GLuint);
+extern void jwzgles_glCullFace (GLuint);
+extern void jwzgles_glDepthFunc (GLuint);
+extern void jwzgles_glDepthMask (GLuint);
+extern void jwzgles_glDisable (GLuint);
+extern void jwzgles_glDrawArrays (GLuint, GLuint, GLuint);
+extern GLboolean jwzgles_glIsEnabled (GLuint);
+extern void jwzgles_glEnable (GLuint);
+extern void jwzgles_glFrontFace (GLuint);
+extern void jwzgles_glHint (GLuint, GLuint);
+extern void jwzgles_glLineWidth (GLfloat);
+extern void jwzgles_glLoadIdentity (void);
+extern void jwzgles_glLogicOp (GLuint);
+extern void jwzgles_glMatrixMode (GLuint);
+extern void jwzgles_glMultMatrixf (const GLfloat *);
+extern void jwzgles_glPointSize (GLfloat);
+extern void jwzgles_glPolygonOffset (GLfloat, GLfloat);
+extern void jwzgles_glPopMatrix (void);
+extern void jwzgles_glPushMatrix (void);
+extern void jwzgles_glScissor (GLuint, GLuint, GLuint, GLuint);
+extern void jwzgles_glShadeModel (GLuint);
+extern void jwzgles_glStencilFunc (GLuint, GLuint, GLuint);
+extern void jwzgles_glStencilMask (GLuint);
+extern void jwzgles_glStencilOp (GLuint, GLuint, GLuint);
+extern void jwzgles_glViewport (GLuint, GLuint, GLuint, GLuint);
+extern void jwzgles_glTranslatef (GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glRotatef (GLfloat, GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glRotated (GLdouble, GLdouble x, GLdouble y, GLdouble z);
+extern void jwzgles_glReadBuffer (GLuint);
+extern void jwzgles_glScalef (GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glColor3f (GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glColor4f (GLfloat, GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glColor3fv (const GLfloat *);
+extern void jwzgles_glColor4fv (const GLfloat *);
+extern void jwzgles_glColor3s (GLshort, GLshort, GLshort);
+extern void jwzgles_glColor4s (GLshort, GLshort, GLshort, GLshort);
+extern void jwzgles_glColor3sv (const GLshort *);
+extern void jwzgles_glColor4sv (const GLshort *);
+extern void jwzgles_glColor3us (GLushort, GLushort, GLushort);
+extern void jwzgles_glColor4us (GLushort, GLushort, GLushort, GLushort);
+extern void jwzgles_glColor3usv (const GLushort *);
+extern void jwzgles_glColor4usv (const GLushort *);
+extern void jwzgles_glColor3d (GLdouble, GLdouble, GLdouble);
+extern void jwzgles_glColor4d (GLdouble, GLdouble, GLdouble, GLdouble);
+extern void jwzgles_glColor3dv (const GLdouble *);
+extern void jwzgles_glColor4dv (const GLdouble *);
+extern void jwzgles_glColor4i (GLint, GLint, GLint, GLint);
+extern void jwzgles_glColor3i (GLint, GLint, GLint);
+extern void jwzgles_glColor3iv (const GLint *);
+extern void jwzgles_glColor4iv (const GLint *);
+extern void jwzgles_glColor4ui (GLuint, GLuint, GLuint, GLuint);
+extern void jwzgles_glColor3ui (GLuint, GLuint, GLuint);
+extern void jwzgles_glColor3uiv (const GLuint *);
+extern void jwzgles_glColor4uiv (const GLuint *);
+extern void jwzgles_glColor4b (GLbyte, GLbyte, GLbyte, GLbyte);
+extern void jwzgles_glColor3b (GLbyte, GLbyte, GLbyte);
+extern void jwzgles_glColor4bv (const GLbyte *);
+extern void jwzgles_glColor3bv (const GLbyte *);
+extern void jwzgles_glColor4ub (GLubyte, GLubyte, GLubyte, GLubyte);
+extern void jwzgles_glColor3ub (GLubyte, GLubyte, GLubyte);
+extern void jwzgles_glColor4ubv (const GLubyte *);
+extern void jwzgles_glColor3ubv (const GLubyte *);
+extern void jwzgles_glMaterialf (GLuint, GLuint, GLfloat);
+extern void jwzgles_glMateriali (GLuint, GLuint, GLuint);
+extern void jwzgles_glMaterialfv (GLuint, GLuint, const GLfloat *);
+extern void jwzgles_glMaterialiv (GLuint, GLuint, const GLint *);
+extern void jwzgles_glFinish (void);
+extern void jwzgles_glFlush (void);
+extern void jwzgles_glPixelStorei (GLuint, GLuint);
+extern void jwzgles_glEnableClientState (GLuint);
+extern void jwzgles_glDisableClientState (GLuint);
+
+extern void jwzgles_glInitNames (void);
+extern void jwzgles_glPushName (GLuint);
+extern GLuint jwzgles_glPopName (void);
+extern GLuint jwzgles_glRenderMode (GLuint);
+extern void jwzgles_glSelectBuffer (GLsizei, GLuint *);
+extern void jwzgles_glLightf (GLenum, GLenum, GLfloat);
+extern void jwzgles_glLighti (GLenum, GLenum, GLint);
+extern void jwzgles_glLightfv (GLenum, GLenum, const GLfloat *);
+extern void jwzgles_glLightiv (GLenum, GLenum, const GLint *);
+extern void jwzgles_glLightModelf (GLenum, GLfloat);
+extern void jwzgles_glLightModeli (GLenum, GLint);
+extern void jwzgles_glLightModelfv (GLenum, const GLfloat *);
+extern void jwzgles_glLightModeliv (GLenum, const GLint *);
+extern void jwzgles_glGenTextures (GLuint, GLuint *);
+extern void jwzgles_glFrustum (GLfloat, GLfloat, GLfloat, GLfloat,
+ GLfloat, GLfloat);
+extern void jwzgles_glOrtho (GLfloat, GLfloat, GLfloat, GLfloat,
+ GLfloat, GLfloat);
+extern void jwzgles_glTexImage1D (GLenum target, GLint level,
+ GLint internalFormat,
+ GLsizei width, GLint border,
+ GLenum format, GLenum type,
+ const GLvoid *pixels);
+extern void jwzgles_glTexImage2D (GLenum target,
+ GLint level,
+ GLint internalFormat,
+ GLsizei width,
+ GLsizei height,
+ GLint border,
+ GLenum format,
+ GLenum type,
+ const GLvoid *data);
+extern void jwzgles_glTexSubImage2D (GLenum target, GLint level,
+ GLint xoffset, GLint yoffset,
+ GLsizei width, GLsizei height,
+ GLenum format, GLenum type,
+ const GLvoid *pixels);
+extern void jwzgles_glCopyTexImage2D (GLenum target, GLint level,
+ GLenum internalformat,
+ GLint x, GLint y,
+ GLsizei width, GLsizei height,
+ GLint border);
+extern void jwzgles_glCopyTexSubImage2D (GLenum target, GLint level,
+ GLint xoff, GLint yoff,
+ GLint x, GLint y,
+ GLsizei width, GLsizei height);
+extern void jwzgles_glInterleavedArrays (GLenum, GLsizei, const GLvoid *);
+extern void jwzgles_glTexEnvf (GLuint, GLuint, GLfloat);
+extern void jwzgles_glTexEnvi (GLuint, GLuint, GLuint);
+extern void jwzgles_glTexParameterf (GLuint, GLuint, GLfloat);
+extern void jwzgles_glTexParameteri (GLuint, GLuint, GLuint);
+extern void jwzgles_glTexGeni (GLenum, GLenum, GLint);
+extern void jwzgles_glTexGenfv (GLenum, GLenum, const GLfloat *);
+extern void jwzgles_glGetTexGenfv (GLenum, GLenum, GLfloat *);
+extern void jwzgles_glRectf (GLfloat, GLfloat, GLfloat, GLfloat);
+extern void jwzgles_glRecti (GLint, GLint, GLint, GLint);
+extern void jwzgles_glLightModelfv (GLenum, const GLfloat *);
+extern void jwzgles_glClearDepth (GLfloat);
+extern GLboolean jwzgles_glIsList (GLuint);
+extern void jwzgles_glColorMaterial (GLenum, GLenum);
+extern void jwzgles_glPolygonMode (GLenum, GLenum);
+extern void jwzgles_glFogf (GLenum, GLfloat);
+extern void jwzgles_glFogi (GLenum, GLint);
+extern void jwzgles_glFogfv (GLenum, const GLfloat *);
+extern void jwzgles_glFogiv (GLenum, const GLint *);
+extern void jwzgles_glAlphaFunc (GLenum, GLfloat);
+extern void jwzgles_glClipPlane (GLenum, const GLdouble *);
+extern void jwzgles_glDrawBuffer (GLenum);
+extern void jwzgles_glDeleteTextures (GLuint, const GLuint *);
+
+extern void jwzgles_gluPerspective (GLdouble fovy, GLdouble aspect,
+ GLdouble near, GLdouble far);
+extern void jwzgles_gluLookAt (GLfloat eyex, GLfloat eyey, GLfloat eyez,
+ GLfloat centerx, GLfloat centery,
+ GLfloat centerz,
+ GLfloat upx, GLfloat upy, GLfloat upz);
+extern GLint jwzgles_gluProject (GLdouble objx, GLdouble objy, GLdouble objz,
+ const GLdouble modelMatrix[16],
+ const GLdouble projMatrix[16],
+ const GLint viewport[4],
+ GLdouble *winx, GLdouble *winy,
+ GLdouble *winz);
+extern int jwzgles_gluBuild2DMipmaps (GLenum target,
+ GLint internalFormat,
+ GLsizei width,
+ GLsizei height,
+ GLenum format,
+ GLenum type,
+ const GLvoid *data);
+extern GLboolean jwzgles_gluCheckExtension (const GLubyte *ext_name,
+ const GLubyte *ext_string);
+extern void jwzgles_glGetFloatv (GLenum pname, GLfloat *params);
+extern void jwzgles_glGetPointerv (GLenum pname, GLvoid **params);
+extern void jwzgles_glGetDoublev (GLenum pname, GLdouble *params);
+extern void jwzgles_glGetIntegerv (GLenum pname, GLint *params);
+extern void jwzgles_glGetBooleanv (GLenum pname, GLboolean *params);
+extern void jwzgles_glVertexPointer (GLuint, GLuint, GLuint, const void *);
+extern void jwzgles_glNormalPointer (GLenum, GLuint, const void *);
+extern void jwzgles_glColorPointer (GLuint, GLuint, GLuint, const void *);
+extern void jwzgles_glTexCoordPointer (GLuint, GLuint, GLuint, const void *);
+extern void jwzgles_glBindBuffer (GLuint, GLuint);
+extern void jwzgles_glBufferData (GLenum, GLsizeiptr, const void *, GLenum);
+extern const char *jwzgles_gluErrorString (GLenum error);
+
+#endif /* __JWZGLES_I_H__ */