From http://www.jwz.org/xscreensaver/xscreensaver-5.16.tar.gz
[xscreensaver] / hacks / glx / jwzgles.c
diff --git a/hacks/glx/jwzgles.c b/hacks/glx/jwzgles.c
new file mode 100644 (file)
index 0000000..e10d2e6
--- /dev/null
@@ -0,0 +1,3510 @@
+/* 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".
+
+   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.x, not 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!)
+
+    - 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:
+
+      blocktube       Uses SPHERE_MAP.
+      bouncingcow     Uses OBJECT_LINEAR (when run with -texture)
+      dnalogo         Uses GLUtesselator.
+      extrusion       Uses all kinds of GLUT crap.
+      flyingtoasters  Uses SPHERE_MAP and OBJECT_LINEAR.
+      jigglypuff      Uses SPHERE_MAP (in chrome mode), GL_LINE (in wireframe)
+      jigsaw          Uses GLUtesselator.
+      lockward        Puts verts in lists without glBegin!
+      pinion         Uses glSelectBuffer and gluPickMatrix for mouse-clicks.
+      pipes           Uses glMap2f for the Utah Teapot.
+      polyhedra       Uses GLUtesselator; also Utah Teapot.
+      skytentacles    Uses glTexImage1D and 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 <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>
+#else
+# include <GL/glx.h>
+# include <GL/glu.h>
+#endif
+
+#include "jwzglesI.h"
+
+#define STRINGIFY(X) #X
+
+#undef countof
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+#undef  Assert
+#define Assert(C,S) do { \
+  if (!(C)) { \
+    fprintf (stderr, "jwzgles: %s\n", S); \
+    abort(); \
+  }} while(0)
+
+
+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; } void_int;
+
+typedef struct {               /* saved args for glDrawArrays */
+  int 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 {       /* saved activity within glNewList */
+  int id;
+  int size, count;
+  list_fn *fns;
+} list;
+
+
+typedef struct {       /* A display list */
+  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_LIGHTING     (1<<3)
+#define ISENABLED_BLEND                (1<<4)
+#define ISENABLED_DEPTH_TEST   (1<<5)
+#define ISENABLED_CULL_FACE    (1<<6)
+#define ISENABLED_NORMALIZE    (1<<7)
+#define ISENABLED_FOG          (1<<8)
+#define ISENABLED_COLMAT       (1<<9)
+#define ISENABLED_VERT_ARRAY   (1<<10)
+#define ISENABLED_NORM_ARRAY   (1<<11)
+#define ISENABLED_TEX_ARRAY    (1<<12)
+#define ISENABLED_COLOR_ARRAY  (1<<13)
+
+
+typedef struct {       /* 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;
+
+} jwzgles_state;
+
+
+static jwzgles_state global_state = { { 0, }, 0, 0, 0, { 0, }, 0, };
+static jwzgles_state *state = &global_state;
+
+
+#ifdef DEBUG
+# define LOG(A)                fprintf(stderr,"jwzgles: " A "\n")
+# define LOG1(A,B)             fprintf(stderr,"jwzgles: " A "\n",B)
+# define LOG2(A,B,C)           fprintf(stderr,"jwzgles: " A "\n",B,C)
+# define LOG3(A,B,C,D)         fprintf(stderr,"jwzgles: " A "\n",B,C,D)
+# define LOG4(A,B,C,D,E)       fprintf(stderr,"jwzgles: " A "\n",B,C,D,E)
+# define LOG5(A,B,C,D,E,F)     fprintf(stderr,"jwzgles: " A "\n",B,C,D,E,F)
+# define LOG6(A,B,C,D,E,F,G)   fprintf(stderr,"jwzgles: " A "\n",B,C,D,E,F,G)
+# define LOG7(A,B,C,D,E,F,G,H) fprintf(stderr,"jwzgles: " A "\n",B,C,D,E,F,G,H)
+# define LOG8(A,B,C,D,E,F,G,H,I)\
+         fprintf(stderr,"jwzgles: "A "\n",B,C,D,E,F,G,H,I)
+# define LOG9(A,B,C,D,E,F,G,H,I,J)\
+         fprintf(stderr,"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)\
+         fprintf(stderr,"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)\
+         fprintf(stderr,"jwzgles: "A "\n",B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R)
+# define CHECK(S) 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(AMBIENT)
+  SS(AMBIENT_AND_DIFFUSE)
+  SS(AUTO_NORMAL)
+  SS(BACK)
+  SS(BLEND)
+  SS(BLEND_DST)
+  SS(BLEND_SRC)
+  SS(BLEND_SRC_ALPHA)
+  SS(RGBA_MODE)
+  SS(DOUBLEBUFFER)
+  SS(GREATER)
+  SS(ALPHA_TEST)
+  SS(LESS)
+  SS(BYTE)
+  SS(C3F_V3F)
+  SS(C4F_N3F_V3F)
+  SS(C4UB_V2F)
+  SS(C4UB_V3F)
+  SS(CCW)
+  SS(CLAMP)
+  SS(COLOR_ARRAY)
+  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(DST_ALPHA)
+  SS(DST_COLOR)
+  SS(EYE_LINEAR)
+  SS(EYE_PLANE)
+  SS(FEEDBACK)
+  SS(FILL)
+  SS(FLAT)
+  SS(FLOAT)
+  SS(FOG)
+  SS(FRONT)
+  SS(FRONT_AND_BACK)
+  SS(INTENSITY)
+  SS(INVALID_ENUM)
+  SS(INVALID_OPERATION)
+  SS(INVALID_VALUE)
+  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(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(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(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_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(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
+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 */
+
+
+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;
+
+      /* Adding a new list at the end.  Make room for it.
+       */
+      if (state->lists.count >= state->lists.size - 1)
+        {
+          int new_size = 20 + (state->lists.size * 1.2);
+          state->lists.lists = (list *) 
+            realloc (state->lists.lists, 
+                     new_size * sizeof (*state->lists.lists));
+          Assert (state->lists.lists, "out of memory");
+          state->lists.size = new_size;
+          LOG1("glGenLists grew -> %d", new_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;
+
+  LOG1("glNewList -> %d", id);
+}
+
+
+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);
+  state->compiling_list = 0;
+}
+
+
+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
+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");
+
+  if (L->count >= L->size - 1)
+    {
+      int new_size = 20 + (L->size * 1.2);
+      L->fns = (list_fn *) realloc (L->fns, new_size * sizeof (*L->fns));
+      Assert (L->fns, "glNewList: no functions");
+      L->size = new_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)
+      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 (lf->arrays[j].data)
+                        free (lf->arrays[j].data);
+                    }
+                }
+            }
+          if (L->fns) 
+            free (L->fns);
+
+          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]);
+
+      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++;
+
+      if (! state->compiling_verts)    /* 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]);
+
+      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++;
+
+      Assert (state->compiling_verts, "glTexCoord4fv outside glBegin");
+    }
+}
+
+
+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);
+}
+
+
+
+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]);
+
+      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++;
+
+      if (! state->compiling_verts)    /* 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)
+{
+  GLfloat vv[4];
+  vv[0] = v[0];
+  vv[1] = v[1];
+  vv[2] = v[2];
+  vv[3] = 1;
+  jwzgles_glColor4fv (vv);
+}
+
+
+void
+jwzgles_glColor4i (GLuint r, GLuint g, GLuint b, GLuint a)
+{
+  jwzgles_glColor4f (r, g, b, a);
+}
+
+
+void
+jwzgles_glColor3i (GLuint r, GLuint g, GLuint b)
+{
+  jwzgles_glColor4f (r, g, b, 1);
+}
+
+
+void
+jwzgles_glColor4iv (const GLint *v)
+{
+  GLfloat vv[4];
+  vv[0] = v[0];
+  vv[1] = v[1];
+  vv[2] = v[2];
+  vv[3] = v[3];
+  jwzgles_glColor4fv (vv);
+}
+
+
+void
+jwzgles_glColor3iv (const GLint *v)
+{
+  GLfloat vv[4];
+  vv[0] = v[0];
+  vv[1] = v[1];
+  vv[2] = v[2];
+  vv[3] = 1;
+  jwzgles_glColor4fv (vv);
+}
+
+
+void
+jwzgles_glColor4ub (GLubyte r, GLubyte g, GLubyte b, GLubyte a)
+{
+  jwzgles_glColor4f (r, g, b, a);
+}
+
+
+void
+jwzgles_glColor3ub (GLubyte r, GLubyte g, GLubyte b)
+{
+  jwzgles_glColor4f (r, g, b, 1);
+}
+
+
+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_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--;
+
+  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! */
+
+  glVertexPointer   (4, GL_FLOAT, sizeof(*s->verts), s->verts);  /* XYZW */
+  glNormalPointer   (   GL_FLOAT, sizeof(*s->norms), s->norms);  /* XYZ  */
+  glTexCoordPointer (4, GL_FLOAT, sizeof(*s->tex),   s->tex);    /* STRQ */
+  glColorPointer    (4, GL_FLOAT, sizeof(*s->color), s->color);  /* RGBA */
+  CHECK("glColorPointer");
+
+  /* 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.  #### Actually this isn't quite right: if glEnd is in a
+     list, it saves the trailing enable/disable calls in the list, instead
+     if restoring them to what their state was before the list was run.
+  */
+  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 (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)
+    {
+      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;
+
+  jwzgles_glDrawArrays (s->mode, 0, s->count);
+
+# 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;
+}
+
+
+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)
+              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) */
+    {
+      glGetIntegerv (GL_VERTEX_ARRAY_SIZE,    &A[i].size);
+      glGetIntegerv (GL_VERTEX_ARRAY_TYPE,    &A[i].type);
+      glGetIntegerv (GL_VERTEX_ARRAY_STRIDE,  &A[i].stride);
+      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;
+      glGetIntegerv (GL_NORMAL_ARRAY_TYPE,    &A[i].type);
+      glGetIntegerv (GL_NORMAL_ARRAY_STRIDE,  &A[i].stride);
+      glGetPointerv (GL_NORMAL_ARRAY_POINTER, &A[i].data);
+      CHECK("glGetPointerv");
+      copy_array_data (&A[i], count, "norm");
+    }
+
+  i++;
+  if (state->set.tcount > 1)
+    {
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_SIZE,    &A[i].size);
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_TYPE,    &A[i].type);
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_STRIDE,  &A[i].stride);
+      glGetPointerv (GL_TEXTURE_COORD_ARRAY_POINTER, &A[i].data);
+      CHECK("glGetPointerv");
+      copy_array_data (&A[i], count, "tex ");
+    }
+
+  i++;
+  if (state->set.ccount > 1)
+    {
+      glGetIntegerv (GL_COLOR_ARRAY_SIZE,    &A[i].size);
+      glGetIntegerv (GL_COLOR_ARRAY_TYPE,    &A[i].type);
+      glGetIntegerv (GL_COLOR_ARRAY_STRIDE,  &A[i].stride);
+      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;
+
+  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 0
+  {
+      unsigned char *b = (unsigned char *) A->data;
+      int i;
+      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;
+
+  if (jwzgles_glIsEnabled (GL_VERTEX_ARRAY))
+    {
+      glGetIntegerv (GL_VERTEX_ARRAY_SIZE,    &A.size);
+      glGetIntegerv (GL_VERTEX_ARRAY_TYPE,    &A.type);
+      glGetIntegerv (GL_VERTEX_ARRAY_STRIDE,  &A.stride);
+      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;
+      glGetIntegerv (GL_NORMAL_ARRAY_TYPE,    &A.type);
+      glGetIntegerv (GL_NORMAL_ARRAY_STRIDE,  &A.stride);
+      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))
+    {
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_SIZE,    &A.size);
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_TYPE,    &A.type);
+      glGetIntegerv (GL_TEXTURE_COORD_ARRAY_STRIDE,  &A.stride);
+      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))
+    {
+      glGetIntegerv (GL_COLOR_ARRAY_SIZE,    &A.size);
+      glGetIntegerv (GL_COLOR_ARRAY_TYPE,    &A.type);
+      glGetIntegerv (GL_COLOR_ARRAY_STRIDE,  &A.stride);
+      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;
+  const void *old;
+  void *data2;
+  const GLfloat *IF;
+  GLfloat *OF;
+  const unsigned char *IB;
+  unsigned char *OB;
+
+  if (! A->data) 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;
+  }
+
+  old = A->data;
+  A->data = data2;
+  A->bytes = bytes;
+  A->stride = stride2;
+
+# ifdef DEBUG
+  dump_array_data (A, count, "saved", name, old);
+# 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;
+      if (! A[i].data) continue;
+      switch (i) {
+      case 0: glVertexPointer  (A[i].size, A[i].type, A[i].stride, A[i].data);
+        name = "vertex ";
+        CHECK("glVertexPointer");
+        break;
+      case 1: glNormalPointer  (           A[i].type, A[i].stride, A[i].data);
+        name = "normal ";
+        CHECK("glNormalPointer");
+        break;
+      case 2: glTexCoordPointer(A[i].size, A[i].type, A[i].stride, A[i].data);
+        name = "texture";
+        CHECK("glTexCoordPointer");
+        break;
+      case 3: 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
+jwzgles_glDrawArrays (GLuint mode, GLuint first, GLuint 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:
+    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:
+    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);
+    glColorPointer (4, GL_UNSIGNED_BYTE, stride, c);
+    CHECK("glColorPointer");
+    c += 4*B;  /* #### might be incorrect float-aligned address */
+    glVertexPointer (2, GL_FLOAT, stride, c);
+    break;
+  case GL_C4UB_V3F:
+    if (stride == 0)
+      stride = 4*B + 3*F;
+    jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+    glColorPointer (4, GL_UNSIGNED_BYTE, stride, c);
+    CHECK("glColorPointer");
+    c += 4*B;
+    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);
+    glColorPointer (3, GL_FLOAT, stride, c);
+    CHECK("glColorPointer");
+    c += 3*F;
+    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);
+    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;
+    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);
+    glColorPointer (4, GL_FLOAT, stride, c);
+    CHECK("glColorPointer");
+    c += 4*F;
+    jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+    glNormalPointer (GL_FLOAT, stride, c);
+    CHECK("glNormalPointer");
+    c += 3*F;
+    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);
+    glTexCoordPointer (2, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 2*F;
+    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);
+    glTexCoordPointer (4, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 4*F;
+    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);
+    glTexCoordPointer (2, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 2*F;
+    jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+    glColorPointer  (4, GL_UNSIGNED_BYTE, stride, c);
+    CHECK("glColorPointer");
+    c += 4*B;
+    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);
+    glTexCoordPointer (2, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 2*F;
+    jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+    glColorPointer  (3, GL_FLOAT, stride, c);
+    CHECK("glColorPointer");
+    c += 3*F;
+    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);
+    glTexCoordPointer (2, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 2*F;
+    jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+    glNormalPointer (GL_FLOAT, stride, c);
+    CHECK("glNormalPointer");
+    c += 3*F;
+    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);
+    glTexCoordPointer (2, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 2*F;
+    jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+    glColorPointer  (3, GL_FLOAT, stride, c);
+    CHECK("glColorPointer");
+    c += 3*F;
+    jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+    glNormalPointer (GL_FLOAT, stride, c);
+    CHECK("glNormalPointer");
+    c += 3*F;
+    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);
+    glTexCoordPointer (4, GL_FLOAT, stride, c);
+    CHECK("glTexCoordPointer");
+    c += 4*F;
+    jwzgles_glEnableClientState (GL_COLOR_ARRAY);
+    glColorPointer  (4, GL_FLOAT, stride, c);
+    CHECK("glColorPointer");
+    c += 4*F;
+    jwzgles_glEnableClientState (GL_NORMAL_ARRAY);
+    glNormalPointer (GL_FLOAT, stride, c);
+    CHECK("glNormalPointer");
+    c += 3*F;
+    glVertexPointer (3, GL_FLOAT, stride, c);
+    CHECK("glVertexPointer");
+    break;
+  default:
+    Assert (0, "glInterleavedArrays: bogus format");
+    break;
+  }
+
+# undef B
+# undef F
+}
+
+
+
+void
+jwzgles_glEnableClientState (GLuint cap)
+{
+  if (state->compiling_list)
+    {
+      void_int vv[1];
+      vv[0].i = cap;
+      list_push ("glEnableClientState", 
+                 (list_fn_cb) &jwzgles_glEnableClientState,
+                 PROTO_I, vv);
+    }
+  else
+    {
+      if (! state->replaying_list)
+        LOG2 ("direct %-12s %s", "glEnableClientState", mode_desc(cap));
+      glEnableClientState (cap);  /* the real one */
+      CHECK("glEnableClientState");
+    }
+
+  switch (cap) {
+  case GL_NORMAL_ARRAY:
+    state->set.ncount += 2;
+    state->enabled |= ISENABLED_NORM_ARRAY;
+    break;
+  case GL_TEXTURE_COORD_ARRAY:
+    state->set.tcount += 2;
+    state->enabled |= ISENABLED_TEX_ARRAY;
+    break;
+  case GL_COLOR_ARRAY:
+    state->set.ccount += 2;
+    state->enabled |= ISENABLED_COLOR_ARRAY;
+    break;
+  default: break;
+  }
+}
+
+
+void
+jwzgles_glDisableClientState (GLuint cap)
+{
+  if (state->compiling_list)
+    {
+      void_int vv[1];
+      vv[0].i = cap;
+      list_push ("glDisableClientState", 
+                 (list_fn_cb) &jwzgles_glDisableClientState,
+                 PROTO_I, vv);
+    }
+  else
+    {
+      if (! state->replaying_list)
+        LOG2 ("direct %-12s %s", "glDisableClientState", mode_desc(cap));
+      glDisableClientState (cap);  /* the real one */
+      CHECK("glDisableClientState");
+    }
+
+  switch (cap) {
+  case GL_NORMAL_ARRAY:
+    state->set.ncount = 0;
+    state->enabled &= ~ISENABLED_NORM_ARRAY;
+    break;
+  case GL_TEXTURE_COORD_ARRAY:
+    state->set.tcount = 0;
+    state->enabled &= ~ISENABLED_TEX_ARRAY;
+    break;
+  case GL_COLOR_ARRAY:
+    state->set.ccount = 0;
+    state->enabled &= ~ISENABLED_COLOR_ARRAY;
+    break;
+  default:
+    break;
+  }
+}
+
+
+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");
+}
+
+
+/* return the next larger power of 2. */
+static int
+to_pow2 (int value)
+{
+  int i = 1;
+  while (i < value) i <<= 1;
+  return i;
+}
+
+void
+jwzgles_glTexImage1D (GLenum target, GLint level,
+                      GLint internalFormat,
+                      GLsizei width, GLint border,
+                      GLenum format, GLenum type,
+                      const GLvoid *pixels)
+{
+  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");
+  Assert (0, "glTexImage1D unimplemented");  /* does not exist in GLES */
+}
+
+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 (! 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_glTexGenfv (GLenum coord, GLenum pname, const GLfloat *params)
+{
+  /* OpenGLES doesn't have this at all!
+     "Oh, just rewrite that code to use GPU shaders", they say.
+     How fucking convenient.
+
+     So, when this is enabled, we could emit a GL_TEXTURE_COORD_ARRAY
+     and compute coords for each vertex in the current GL_VERTEX_ARRAY
+     as per http://www.opengl.org/wiki/Mathematics_of_glTexGen
+     but holy shit, what a pain in the ass!
+
+     For GL_OBJECT_LINEAR, we can just re-use the vertex array as
+     the texture array, using a proper stride.  That's hardly worth
+     the effort, though, because bouncingcow is the only hack that
+     uses that, and not even by default.
+   */
+  Assert (coord == GL_S || coord == GL_T, "glTexGenfv: unimplemented coord");
+
+  /* This is probably default-ish, so do nothing. */
+  if (pname == GL_EYE_PLANE) return;
+
+  Assert (pname == GL_TEXTURE_GEN_MODE, "glTexGenfv: unimplemented name");
+  Assert (params[0] == GL_EYE_LINEAR, "glTexGenfv: unimplemented mode");
+}
+
+void
+jwzgles_glTexGeni (GLenum coord, GLenum pname, GLint param)
+{
+  GLfloat v = param;
+  jwzgles_glTexGenfv (coord, pname, &v);
+}
+
+
+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().
+   */
+
+  int w2 = to_pow2(width);
+  int h2 = 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");
+}
+
+
+void
+jwzgles_glEnable (GLuint bit)
+{
+  Assert (!state->compiling_verts, "glEnable not allowed inside glBegin");
+  if (state->compiling_list)
+    {
+      void_int vv[1];
+      vv[0].i = bit;
+      list_push ("glEnable", (list_fn_cb) &jwzgles_glEnable, PROTO_I, vv);
+    }
+  else
+    {
+      if (! state->replaying_list)
+        LOG2 ("direct %-12s %s", "glEnable", mode_desc(bit));
+      glEnable (bit);  /* the real one */
+      CHECK("glEnable");
+
+      switch (bit) {
+      case GL_TEXTURE_2D: state->enabled |= ISENABLED_TEXTURE_2D; break;
+      case GL_TEXTURE_GEN_S: state->enabled |= ISENABLED_TEXTURE_GEN_S; break;
+      case GL_TEXTURE_GEN_T: state->enabled |= ISENABLED_TEXTURE_GEN_T; break;
+      case GL_LIGHTING: state->enabled |= ISENABLED_LIGHTING; break;
+      case GL_BLEND: state->enabled |= ISENABLED_BLEND; break;
+      case GL_DEPTH_TEST: state->enabled |= ISENABLED_DEPTH_TEST; break;
+      case GL_CULL_FACE: state->enabled |= ISENABLED_CULL_FACE; break;
+      case GL_NORMALIZE: state->enabled |= ISENABLED_NORMALIZE; break;
+      case GL_FOG: state->enabled |= ISENABLED_FOG; break;
+      case GL_COLOR_MATERIAL: state->enabled |= ISENABLED_COLMAT; break;
+
+      /* Do these work with glEnable or only with glEnableClientState? */
+      case GL_VERTEX_ARRAY: state->enabled |= ISENABLED_VERT_ARRAY; break;
+      case GL_NORMAL_ARRAY: state->enabled |= ISENABLED_NORM_ARRAY; break;
+      case GL_TEXTURE_COORD_ARRAY: state->enabled |= ISENABLED_TEX_ARRAY;break;
+      case GL_COLOR_ARRAY:  state->enabled |= ISENABLED_COLOR_ARRAY; break;
+
+      default: break;
+      }
+    }
+}
+
+
+void
+jwzgles_glDisable (GLuint bit)
+{
+  Assert (!state->compiling_verts, "glDisable not allowed inside glBegin");
+  if (state->compiling_list)
+    {
+      void_int vv[1];
+      vv[0].i = bit;
+      list_push ("glDisable", (list_fn_cb) &jwzgles_glDisable, PROTO_I, vv);
+    }
+  else
+    {
+      if (! state->replaying_list)
+        LOG2 ("direct %-12s %s", "glDisable", mode_desc(bit));
+      glDisable (bit);  /* the real one */
+      CHECK("glDisable");
+
+      switch (bit) {
+      case GL_TEXTURE_2D: state->enabled &= ~ISENABLED_TEXTURE_2D; break;
+      case GL_TEXTURE_GEN_S: state->enabled &= ~ISENABLED_TEXTURE_GEN_S; break;
+      case GL_TEXTURE_GEN_T: state->enabled &= ~ISENABLED_TEXTURE_GEN_T; break;
+      case GL_LIGHTING: state->enabled &= ~ISENABLED_LIGHTING; break;
+      case GL_BLEND: state->enabled &= ~ISENABLED_BLEND; break;
+      case GL_DEPTH_TEST: state->enabled &= ~ISENABLED_DEPTH_TEST; break;
+      case GL_CULL_FACE: state->enabled &= ~ISENABLED_CULL_FACE; break;
+      case GL_NORMALIZE: state->enabled &= ~ISENABLED_NORMALIZE; break;
+      case GL_FOG: state->enabled &= ~ISENABLED_FOG; break;
+      case GL_COLOR_MATERIAL: state->enabled &= ~ISENABLED_COLMAT; break;
+
+      /* Do these work with glEnable or only with glEnableClientState? */
+      case GL_VERTEX_ARRAY: state->enabled &= ~ISENABLED_VERT_ARRAY; break;
+      case GL_NORMAL_ARRAY: state->enabled &= ~ISENABLED_NORM_ARRAY; break;
+      case GL_TEXTURE_COORD_ARRAY: state->enabled &= ~ISENABLED_TEX_ARRAY;break;
+      case GL_COLOR_ARRAY:  state->enabled &= ~ISENABLED_COLOR_ARRAY; break;
+
+      default: break;
+      }
+    }
+}
+
+
+GLboolean
+jwzgles_glIsEnabled (GLuint bit)
+{
+ /*
+  Assert (!state->compiling_verts, "glIsEnabled not allowed inside glBegin");
+  Assert (!state->compiling_list,  "glIsEnabled not allowed inside glNewList");
+  */
+  switch (bit) {
+  case GL_TEXTURE_2D: return !!(state->enabled & ISENABLED_TEXTURE_2D);
+  case GL_TEXTURE_GEN_S: return !!(state->enabled & ISENABLED_TEXTURE_GEN_S);
+  case GL_TEXTURE_GEN_T: return !!(state->enabled & ISENABLED_TEXTURE_GEN_T);
+  case GL_LIGHTING: return !!(state->enabled & ISENABLED_LIGHTING);
+  case GL_BLEND: return !!(state->enabled & ISENABLED_BLEND);
+  case GL_DEPTH_TEST: return !!(state->enabled & ISENABLED_DEPTH_TEST);
+  case GL_CULL_FACE: return !!(state->enabled & ISENABLED_CULL_FACE);
+  case GL_NORMALIZE: return !!(state->enabled & ISENABLED_NORMALIZE);
+  case GL_FOG: return !!(state->enabled & ISENABLED_FOG);
+  case GL_COLOR_MATERIAL: return !!(state->enabled & ISENABLED_COLMAT);
+
+  /* Do these work with glEnable or only with glEnableClientState?
+     We need to query them, and there is no glIsClientStateEnabled.
+   */
+  case GL_VERTEX_ARRAY: return !!(state->enabled & ISENABLED_VERT_ARRAY);
+  case GL_NORMAL_ARRAY: return !!(state->enabled & ISENABLED_NORM_ARRAY);
+  case GL_TEXTURE_COORD_ARRAY: return !!(state->enabled & ISENABLED_TEX_ARRAY);
+  case GL_COLOR_ARRAY: return !!(state->enabled & ISENABLED_COLOR_ARRAY);
+  default: Assert (0, "glIsEnabled unimplemented bit"); break;
+  }
+}
+
+
+/* The spec says that OpenGLES 1.x doesn't implement glGetFloatv.
+   Were this true, it would suck, for it would mean that there was 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.
+
+   However, Apple's iOS OpenGLES *does* provide glGetFloatv!
+ */
+void
+jwzgles_glGetFloatv (GLenum pname, GLfloat *params)
+{
+  if (! state->replaying_list)
+    LOG2 ("direct %-12s %s", "glGetFloatv", mode_desc(pname));
+  glGetFloatv (pname, params);  /* the real one */
+  CHECK("glGetFloatv");
+}
+
+
+/* Likewise: not supposed to be there, but it is. */
+void
+jwzgles_glGetPointerv (GLenum pname, GLvoid *params)
+{
+  if (! state->replaying_list)
+    LOG2 ("direct %-12s %s", "glGetPointerv", mode_desc(pname));
+  glGetPointerv (pname, params);  /* the real one */
+  CHECK("glGetPointerv");
+}
+
+
+/* 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 can be included inside glNewList, but they actually execute
+   immediately anyway. 
+ */
+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);
+  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);
+  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);
+  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);
+  glTexCoordPointer (size, type, stride, ptr);  /* the real one */
+  CHECK("glTexCoordPointer");
+}
+
+
+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;
+
+  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);
+}
+
+
+/* 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);
+}
+
+
+/* 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 (glBindTexture,   II)
+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)
+WRAP (glViewport,      IIII)
+#undef  TYPE_IV
+#define TYPE_IV GLuint
+WRAP (glDeleteTextures,        IIV)
+
+
+#endif /* HAVE_JWZGLES - whole file */