diff options
author | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
---|---|---|
committer | Simon Rettberg | 2018-10-16 10:08:48 +0200 |
commit | d3a98cf6cbc3bd0b9efc570f58e8812c03931c18 (patch) | |
tree | cbddf8e50f35a9c6e878a5bfe3c6d625d99e12ba /jwxyz | |
download | xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.gz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.tar.xz xscreensaver-d3a98cf6cbc3bd0b9efc570f58e8812c03931c18.zip |
Original 5.40
Diffstat (limited to 'jwxyz')
-rw-r--r-- | jwxyz/Makefile.in | 141 | ||||
-rw-r--r-- | jwxyz/README | 30 | ||||
-rw-r--r-- | jwxyz/jwxyz-android.c | 1724 | ||||
-rw-r--r-- | jwxyz/jwxyz-android.h | 121 | ||||
-rw-r--r-- | jwxyz/jwxyz-cocoa.h | 98 | ||||
-rw-r--r-- | jwxyz/jwxyz-cocoa.m | 1043 | ||||
-rw-r--r-- | jwxyz/jwxyz-common.c | 1828 | ||||
-rw-r--r-- | jwxyz/jwxyz-gl.c | 2125 | ||||
-rw-r--r-- | jwxyz/jwxyz-image.c | 527 | ||||
-rw-r--r-- | jwxyz/jwxyz-timers.c | 357 | ||||
-rw-r--r-- | jwxyz/jwxyz-timers.h | 25 | ||||
-rw-r--r-- | jwxyz/jwxyz.h | 906 | ||||
-rw-r--r-- | jwxyz/jwxyz.m | 1812 | ||||
-rw-r--r-- | jwxyz/jwxyzI.h | 208 | ||||
-rw-r--r-- | jwxyz/jwzgles.c | 4331 | ||||
-rw-r--r-- | jwxyz/jwzgles.h | 520 | ||||
-rw-r--r-- | jwxyz/jwzglesI.h | 355 |
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 ← 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__ */ |