/* xscreensaver, Copyright (c) 1991-2020 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or 
 * implied warranty.
 */

/* JWXYZ Is Not Xlib.

   Pixmaps implemented in terms of OpenGL textures, for Android X11 hacks.
   Maybe this can someday be used on macOS and iOS as well.

   See the comment at the top of jwxyz-common.c for an explanation of
   the division of labor between these various modules.
 */


/* Be advised, this is all very much a work in progress. */

/* There is probably no reason to ever implement indexed-color rendering here,
   even if many screenhacks still work with PseudoColor.
   - OpenGL ES 1.1 (Android, iOS) doesn't support indexed color.
   - macOS only provides indexed color via AGL (now deprecated), not
     NSOpenGLPixelFormat.
 */

/* TODO:
   - malloc error checking
   - Check max texture sizes for XGet/PutImage, XCopyArea.
   - Optional 5:5:5 16-bit color
*/

/* Techniques & notes:
   - iOS: OpenGL ES 2.0 isn't always available. Use OpenGL ES 1.1.
   - OS X: Drivers can go back to OpenGL 1.1 (GeForce 2 MX on 10.5.8).
   - Use stencil buffers (OpenGL 1.0+) for bitmap clipping masks.
   - Pixmaps can be any of the following, depending on GL implementation.
     - This requires offscreen rendering. Fortunately, this is always
       available.
       - OS X: Drawable objects, including: pixel buffers and offscreen
         memory.
         - Offscreen buffers w/ CGLSetOffScreen (10.0+)
         - http://lists.apple.com/archives/mac-opengl/2002/Jun/msg00087.html
           provides a very ugly solution for hardware-accelerated offscreen
           rendering with CGLSetParameter(*, kCGLCPSurfaceTexture, *) on OS X
           10.1+. Apple docs say it's actually for OS X 10.3+, instead.
         - Pixel buffers w/ CGLSetPBuffer (10.3+, now deprecated)
           - Requires APPLE_pixel_buffer.
             - Available in software on x86 only.
             - Always available on hardware.
           - Need to blit? Use OpenGL pixel buffers. (GL_PIXEL_PACK_BUFFER)
         - Framebuffer objects w/ GL_(ARB|EXT)_framebuffer_object
           - TODO: Can EXT_framebuffers be different sizes?
           - Preferred on 10.7+
       - iOS: Use OES_framebuffer_object, it's always present.
 */

/* OpenGL hacks call a number of X11 functions, including
   XCopyArea, XDrawString, XGetImage
   XCreatePixmap, XCreateGC, XCreateImage
   XPutPixel
   Check these, of course.
 */

#ifdef JWXYZ_GL /* entire file */

#include <math.h>
#include <limits.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>

#ifdef HAVE_COCOA
# ifdef HAVE_IPHONE
#  import <QuartzCore/QuartzCore.h>
#  include <OpenGLES/ES1/gl.h>
#  include <OpenGLES/ES1/glext.h>
# else
#  include <OpenGL/glu.h>
# endif
#else
/* TODO: Does this work on iOS? */
# ifndef HAVE_JWZGLES
#  include <gl/glu.h>
# else
#  include <GLES/gl.h>
#  include <GLES/glext.h>
#  ifdef HAVE_GLES3
#   include <GLES3/gl3.h>
#  endif
# endif
#endif

#ifdef HAVE_JWZGLES
# include "jwzglesI.h"
#endif

#include "jwxyzI.h"
#include "jwxyz-timers.h"
#include "yarandom.h"
#include "utf8wc.h"
#include "xft.h"
#include "pow2.h"

#define countof(x) (sizeof((x))/sizeof((*x)))

union color_bytes {
  uint32_t pixel;
  uint8_t bytes[4];
};

// Use two textures: one for RGBA, one for luminance. Older Android doesn't
// seem to like it when textures change format.
enum {
  texture_rgba,
  texture_mono
};

struct jwxyz_Display {
  const struct jwxyz_vtbl *vtbl; // Must come first.

  Window main_window;
  GLenum pixel_format, pixel_type;
  Visual visual;
  struct jwxyz_sources_data *timers_data;

  Bool gl_texture_npot_p;
  /* Bool opengl_core_p */;
  GLenum gl_texture_target;
  
  GLuint textures[2]; // Also can work on the desktop.

  unsigned long window_background;

  int gc_function;
  Bool gc_alpha_allowed_p;
  int gc_clip_x_origin, gc_clip_y_origin;
  GLuint gc_clip_mask;

  // Alternately, there could be one queue per pixmap.
  size_t queue_size, queue_capacity;
  Drawable queue_drawable;
  GLint queue_mode;
  void *queue_vertex;
  uint32_t *queue_color;
  Bool queue_line_cap;
};

struct jwxyz_GC {
  XGCValues gcv;
  unsigned int depth;
  GLuint clip_mask;
  unsigned clip_mask_width, clip_mask_height;
};

struct jwxyz_linked_point {
    short x, y;
    linked_point *next;
};


void
jwxyz_assert_display(Display *dpy)
{
  if(!dpy)
    return;
  jwxyz_assert_gl ();
  jwxyz_assert_drawable (dpy->main_window, dpy->main_window);
}


void
jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
                    Bool window_p)
{
  Assert (width, "no width");
  Assert (height, "no height");

  /* TODO: Check registration pattern from Interference with rectangles instead of points. */

  // The projection matrix is always set as follows. The modelview matrix is
  // usually identity, but for points and thin lines, it's translated by 0.5.
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
# if defined(HAVE_IPHONE) || defined(HAVE_ANDROID)

  if (window_p && ignore_rotation_p(dpy)) {
    int o = (int) current_device_rotation();
    glRotatef (-o, 0, 0, 1);
  }

  // glPointSize(1); // This is the default.
  
#ifdef HAVE_JWZGLES
  glOrthof /* TODO: Is glOrthox worth it? Signs point to no. */
#else
  glOrtho
#endif
    (0, width, height, 0, -1, 1);

  glMatrixMode(GL_MODELVIEW);
# endif // HAVE_MOBILE
}

#ifndef HAVE_JWZGLES

struct gl_version
{
  // iOS always uses OpenGL ES 1.1.
  unsigned major;
  unsigned minor;
};

static GLboolean
gl_check_ver (const struct gl_version *caps,
              unsigned gl_major,
              unsigned gl_minor)
{
  return caps->major > gl_major ||
           (caps->major == gl_major && caps->minor >= gl_minor);
}

#endif


static void
tex_parameters (Display *d, GLuint texture)
{
  // TODO: Check for (ARB|EXT|NV)_texture_rectangle. (All three are alike.)
  // Rectangle textures should be present on OS X with the following exceptions:
  // - Generic renderer on PowerPC OS X 10.4 and earlier
  // - ATI Rage 128
  glBindTexture (d->gl_texture_target, texture);
  // TODO: This is all somewhere else. Refactor.
  glTexParameteri (d->gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (d->gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

  // This might be redundant for rectangular textures.
# ifndef HAVE_JWZGLES
  const GLint wrap = GL_CLAMP;
# else  // HAVE_JWZGLES
  const GLint wrap = GL_CLAMP_TO_EDGE;
# endif // HAVE_JWZGLES

  // In OpenGL, CLAMP_TO_EDGE is OpenGL 1.2 or GL_SGIS_texture_edge_clamp.
  // This is always present with OpenGL ES.
  glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_S, wrap);
  glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_T, wrap);
}

static void
tex_size (Display *dpy, unsigned *tex_w, unsigned *tex_h)
{
  if (!dpy->gl_texture_npot_p) {
    *tex_w = to_pow2(*tex_w);
    *tex_h = to_pow2(*tex_h);
  }
}

static void
tex_image (Display *dpy, GLenum internalformat,
           unsigned *tex_w, unsigned *tex_h, GLenum format, GLenum type,
           const void *data)
{
  unsigned w = *tex_w, h = *tex_h;
  tex_size (dpy, tex_w, tex_h);

  // TODO: Would using glTexSubImage2D exclusively be faster?
  if (*tex_w == w && *tex_h == h) {
    glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
                  0, format, type, data);
  } else {
    // TODO: Sampling the last row might be a problem if src_x != 0.
    glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
                  0, format, type, NULL);
    glTexSubImage2D (dpy->gl_texture_target, 0, 0, 0, w, h,
                     format, type, data);
  }
}


extern const struct jwxyz_vtbl gl_vtbl;

Display *
jwxyz_gl_make_display (Window w)
{
  Display *d = (Display *) calloc (1, sizeof(*d));
  d->vtbl = &gl_vtbl;

# ifndef HAVE_JWZGLES
  struct gl_version version;

  {
    const GLubyte *version_str = glGetString (GL_VERSION);

    /* iPhone is always OpenGL ES 1.1. */
    if (sscanf ((const char *) version_str, "%u.%u",
                &version.major, &version.minor) < 2)
    {
      version.major = 1;
      version.minor = 1;
    }
  }
# endif // !HAVE_JWZGLES

  const GLubyte *extensions = glGetString (GL_EXTENSIONS);

  /* See:
     - Apple TN2080: Understanding and Detecting OpenGL Functionality.
     - OpenGL Programming Guide for the Mac - Best Practices for Working with
       Texture Data - Optimal Data Formats and Types
   */

  // If a video adapter suports BGRA textures, then that's probably as fast as
  // you're gonna get for getting a texture onto the screen.
# ifdef HAVE_JWZGLES
  /* TODO: Make BGRA work on iOS. As it is, it breaks XPutImage. (glTexImage2D, AFAIK) */
  d->pixel_format = GL_RGBA; /*
    gluCheckExtension ((const GLubyte *) "GL_APPLE_texture_format_BGRA8888",
                       extensions) ? GL_BGRA_EXT : GL_RGBA; */
  d->pixel_type = GL_UNSIGNED_BYTE;
  // See also OES_read_format.
# else  // !HAVE_JWZGLES
  if (gl_check_ver (&version, 1, 2) ||
      (gluCheckExtension ((const GLubyte *) "GL_EXT_bgra", extensions) &&
       gluCheckExtension ((const GLubyte *) "GL_APPLE_packed_pixels",
                          extensions))) {
    // APPLE_packed_pixels is only ever available on iOS, never Android.
    d->pixel_format = GL_BGRA_EXT;
    // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
    d->pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
  } else {
    d->pixel_format = GL_RGBA;
    d->pixel_type = GL_UNSIGNED_BYTE;
  }
  // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
  // sense on PowerPC.
# endif // !HAVE_JWZGLES

  // On really old systems, it would make sense to split textures
  // into subsections, to work around the maximum texture size.
# ifndef HAVE_JWZGLES
  d->gl_texture_npot_p = gluCheckExtension ((const GLubyte *)
                                            "GL_ARB_texture_rectangle",
                                            extensions);
  d->gl_texture_target = d->gl_texture_npot_p ?
                         GL_TEXTURE_RECTANGLE_EXT :
                         GL_TEXTURE_2D;
# else
  d->gl_texture_npot_p = jwzgles_gluCheckExtension
      ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions) ||
    jwzgles_gluCheckExtension
      ((const GLubyte *) "GL_OES_texture_npot", extensions) ||
    jwzgles_gluCheckExtension // From PixelFlinger 1.4
      ((const GLubyte *) "GL_ARB_texture_non_power_of_two", extensions);

  d->gl_texture_target = GL_TEXTURE_2D;
# endif

  Visual *v = &d->visual;
  v->class      = TrueColor;
  if (d->pixel_format == GL_BGRA_EXT) {
    v->red_mask   = 0x00ff0000;
    v->green_mask = 0x0000ff00;
    v->blue_mask  = 0x000000ff;
    v->alpha_mask = 0xff000000;
  } else {
    Assert(d->pixel_format == GL_RGBA,
           "jwxyz_gl_make_display: Unknown pixel_format");
    unsigned long masks[4];
    for (unsigned i = 0; i != 4; ++i) {
      union color_bytes color;
      color.pixel = 0;
      color.bytes[i] = 0xff;
      masks[i] = color.pixel;
    }
    v->red_mask   = masks[0];
    v->green_mask = masks[1];
    v->blue_mask  = masks[2];
    v->alpha_mask = masks[3];
  }

  d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));

  d->window_background = BlackPixel(d,0);

  d->main_window = w;

  {
    GLint max_texture_size, max_texture_units;
    glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
    glGetIntegerv (GL_MAX_TEXTURE_UNITS, &max_texture_units);
    Log ("GL_MAX_TEXTURE_SIZE: %d, GL_MAX_TEXTURE_UNITS: %d\n",
         max_texture_size, max_texture_units);

    // OpenGL ES 1.1 and OpenGL 1.3 both promise at least 2 texture units:
    // OpenGL (R) ES Common/Common-Lite Profile Specification, Version 1.1.12 (Full Specification)
    // https://www.khronos.org/registry/OpenGL/specs/es/1.1/es_full_spec_1.1.pdf
    // * Table 6.22. Implementation Dependent Values
    // * D.2 Enhanced Texture Processing
    // (OpenGL 1.2 provides multitexturing as an ARB extension, and requires 1
    // texture unit only.)

    // ...but the glGet reference page contradicts this, and says there can be
    // just one.
    // https://www.khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/glGet.xml
  }

  glGenTextures (countof (d->textures), d->textures);

  for (unsigned i = 0; i != countof (d->textures); i++) {
    tex_parameters (d, d->textures[i]);
  }

  d->gc_function = GXcopy;
  d->gc_alpha_allowed_p = False;
  d->gc_clip_mask = 0;

  jwxyz_assert_display(d);
  return d;
}

void
jwxyz_gl_free_display (Display *dpy)
{
  Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");

  /* TODO: Go over everything. */

  free (dpy->queue_vertex);
  free (dpy->queue_color);

  jwxyz_sources_free (dpy->timers_data);

  free (dpy);
}


/* Call this when the View changes size or position.
 */
void
jwxyz_window_resized (Display *dpy)
{
  Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");

  const XRectangle *new_frame = jwxyz_frame (dpy->main_window);
  unsigned new_width = new_frame->width;
  unsigned new_height = new_frame->height;

  Assert (new_width, "jwxyz_window_resized: No width.");
  Assert (new_height, "jwxyz_window_resized: No height.");

/*if (cgc) w->cgc = cgc;
  Assert (w->cgc, "no CGContext"); */

  Log("resize: %d, %d\n", new_width, new_height);

  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, dpy->main_window, dpy->main_window);

  // TODO: What does the iPhone need?
  
  // iOS only: If the main_window is not the current_drawable, then set_matrices
  // was already called in bind_drawable.
  jwxyz_set_matrices (dpy, new_width, new_height, True);

/*
  GLint draw_buffer;
  glGetIntegerv (GL_DRAW_BUFFER, &draw_buffer);
  
  glDrawBuffer (GL_FRONT);
  glClearColor (1, 0, 1, 0);
  glClear (GL_COLOR_BUFFER_BIT);
  glDrawBuffer (GL_BACK);
  glClearColor (0, 1, 1, 0);
  glClear (GL_COLOR_BUFFER_BIT);
  
  glDrawBuffer (draw_buffer); */
  
  // Stylish and attractive purple!
  // glClearColor (1, 0, 1, 0.5);
  // glClear (GL_COLOR_BUFFER_BIT);
}


static jwxyz_sources_data *
display_sources_data (Display *dpy)
{
  return dpy->timers_data;
}


static Window
root (Display *dpy)
{
  return dpy->main_window;
}

static Visual *
visual (Display *dpy)
{
  return &dpy->visual;
}


/* GC attributes by usage and OpenGL implementation:

   All drawing functions:
   function                                | glLogicOp w/ GL_COLOR_LOGIC_OP
   clip_x_origin, clip_y_origin, clip_mask | Multitexturing w/ GL_TEXTURE1

   Shape drawing functions:
   foreground, background                  | glColor*

   XDrawLines, XDrawRectangles, XDrawSegments:
   line_width, cap_style, join_style       | Lotsa vertices

   XFillPolygon:
   fill_rule                               | Multiple GL_TRIANGLE_FANs

   XDrawText:
   font                                    | Cocoa, then OpenGL display lists.

   alpha_allowed_p                         | GL_BLEND

   antialias_p                             | Well, there's options:
   * Multisampling would work, but that's something that would need to be set
     per-Pixmap, not per-GC.
   * GL_POINT, LINE, and POLYGON_SMOOTH are the old-school way of doing
     this, but POINT_SMOOTH is unnecessary, and POLYGON_SMOOTH is missing from
     GLES 1. All three are missing from GLES 2. Word on the street is that
     these are deprecated anyway.
   * Tiny textures with bilinear filtering to get the same effect as LINE_ and
     POLYGON_SMOOTH. A bit tricky.
   * Do nothing. Android hardware is very often high-DPI enough that
     anti-aliasing doesn't matter all that much.

   Nothing, really:
   subwindow_mode
 */

static void *
enqueue (Display *dpy, Drawable d, GC gc, GLenum mode, size_t count,
         unsigned long pixel)
{
  if (dpy->queue_size &&
      (!gc || /* Could allow NULL GCs here... */
       dpy->gc_function != gc->gcv.function ||
       dpy->gc_alpha_allowed_p != gc->gcv.alpha_allowed_p ||
       dpy->gc_clip_mask != gc->clip_mask ||
       dpy->gc_clip_x_origin != gc->gcv.clip_x_origin ||
       dpy->gc_clip_y_origin != gc->gcv.clip_y_origin ||
       dpy->queue_mode != mode ||
       dpy->queue_drawable != d)) {
    jwxyz_gl_flush (dpy);
  }

  jwxyz_bind_drawable (dpy, dpy->main_window, d);
  jwxyz_gl_set_gc (dpy, gc);

  if (mode == GL_TRIANGLE_STRIP)
    Assert (count, "empty triangle strip");
  // Use degenerate triangles to cut down on draw calls.
  Bool prepend2 = mode == GL_TRIANGLE_STRIP && dpy->queue_size;

  // ####: Primitive restarts should be used here when (if) they're available.
  if (prepend2)
    count += 2;

  // TODO: Use glColor when we can get away with it.
  size_t old_size = dpy->queue_size;
  dpy->queue_size += count;
  if (dpy->queue_size > dpy->queue_capacity) {
    dpy->queue_capacity = dpy->queue_size * 2;

    uint32_t *new_color = realloc (
      dpy->queue_color, sizeof(*dpy->queue_color) * dpy->queue_capacity);
    /* Allocate vertices as if they were always GLfloats. Otherwise, if
       queue_vertex is allocated to hold GLshorts, then things get switched
       to GLfloats, queue_vertex would be too small for the given capacity.
     */
    GLshort *new_vertex = realloc (
      dpy->queue_vertex, sizeof(GLfloat) * 2 * dpy->queue_capacity);

    if (!new_color || !new_vertex)
      return NULL;

    dpy->queue_color = new_color;
    dpy->queue_vertex = new_vertex;
  }

  dpy->queue_mode = mode;
  dpy->queue_drawable = d;

  union color_bytes color;

  // Like query_color, but for bytes.
  jwxyz_validate_pixel (dpy, pixel, jwxyz_drawable_depth (d),
                        gc ? gc->gcv.alpha_allowed_p : False);

  if (jwxyz_drawable_depth (d) == 1) {
    uint8_t b = pixel ? 0xff : 0;
    color.bytes[0] = b;
    color.bytes[1] = b;
    color.bytes[2] = b;
    color.bytes[3] = 0xff;
  } else {
    JWXYZ_QUERY_COLOR (dpy, pixel, 0xffull, color.bytes);
  }

  for (size_t i = 0; i != count; ++i) // TODO: wmemset when applicable.
    dpy->queue_color[i + old_size] = color.pixel;

  void *result = (char *)dpy->queue_vertex + old_size * 2 *
    (mode == GL_TRIANGLE_STRIP ? sizeof(GLfloat) : sizeof(GLshort));
  if (prepend2) {
    dpy->queue_color[old_size] = dpy->queue_color[old_size - 1];
    result = (GLfloat *)result + 4;
  }
  return result;
}


static void
finish_triangle_strip (Display *dpy, GLfloat *enqueue_result)
{
  if (enqueue_result != dpy->queue_vertex) {
    enqueue_result[-4] = enqueue_result[-6];
    enqueue_result[-3] = enqueue_result[-5];
    enqueue_result[-2] = enqueue_result[0];
    enqueue_result[-1] = enqueue_result[1];
  }
}


static void
query_color (Display *dpy, unsigned long pixel, unsigned int depth,
             Bool alpha_allowed_p, GLfloat *rgba)
{
  jwxyz_validate_pixel (dpy, pixel, depth, alpha_allowed_p);

  if (depth == 1) {
    GLfloat f = pixel;
    rgba[0] = f;
    rgba[1] = f;
    rgba[2] = f;
    rgba[3] = 1;
  } else {
    JWXYZ_QUERY_COLOR (dpy, pixel, 1.0f, rgba);
  }
}


static void
set_color (Display *dpy, unsigned long pixel, unsigned int depth,
           Bool alpha_allowed_p)
{
  GLfloat rgba[4];
  query_color (dpy, pixel, depth, alpha_allowed_p, rgba);
  glColor4f (rgba[0], rgba[1], rgba[2], rgba[3]);
}

/* Pushes a GC context; sets Function and ClipMask. */
void
jwxyz_gl_set_gc (Display *dpy, GC gc)
{
  int function;
  Bool alpha_allowed_p;
  GLuint clip_mask;

  // GC is NULL for XClearArea and XClearWindow.
  if (gc) {
    function = gc->gcv.function;
    alpha_allowed_p = gc->gcv.alpha_allowed_p || gc->clip_mask;
    clip_mask = gc->clip_mask;
  } else {
    function = GXcopy;
    alpha_allowed_p = False;
    clip_mask = 0;
  }

  /* GL_COLOR_LOGIC_OP: OpenGL 1.1. */
  if (function != dpy->gc_function) {
    dpy->gc_function = function;
    if (function != GXcopy) {
      /* Fun fact: The glLogicOp opcode constants are the same as the X11 GX*
         function constants | GL_CLEAR.
       */
      glEnable (GL_COLOR_LOGIC_OP);
      glLogicOp (gc->gcv.function | GL_CLEAR);
    } else {
      glDisable (GL_COLOR_LOGIC_OP);
    }
  }

  /* Cocoa uses add/subtract/difference blending in place of logical ops.
     It looks nice, but implementing difference blending in OpenGL appears to
     require GL_KHR_blend_equation_advanced, and support for this is not
     widespread.
   */

  dpy->gc_alpha_allowed_p = alpha_allowed_p;
  if (alpha_allowed_p || clip_mask) {
    // TODO: Maybe move glBlendFunc to XCreatePixmap?
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable (GL_BLEND);
  } else {
    glDisable (GL_BLEND);
  }

  /* Texture units:
     GL_TEXTURE0: Texture for XPutImage/XCopyArea (if applicable)
     GL_TEXTURE1: Texture for clip masks (if applicable)
   */
  dpy->gc_clip_mask = clip_mask;

  glActiveTexture (GL_TEXTURE1);
  if (clip_mask) {
    glEnable (dpy->gl_texture_target);
    glBindTexture (dpy->gl_texture_target, gc->clip_mask);

    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
               alpha_allowed_p ? GL_MODULATE : GL_REPLACE);

    glMatrixMode (GL_TEXTURE);
    glLoadIdentity ();

    unsigned
      tex_w = gc->clip_mask_width + 2, tex_h = gc->clip_mask_height + 2;
    tex_size (dpy, &tex_w, &tex_h);

# ifndef HAVE_JWZGLES
    if (dpy->gl_texture_target == GL_TEXTURE_RECTANGLE_EXT)
    {
      glScalef (1, -1, 1);
    }
    else
# endif
    {
      glScalef (1.0f / tex_w, -1.0f / tex_h, 1);
    }

    glTranslatef (1 - gc->gcv.clip_x_origin,
                  1 - gc->gcv.clip_y_origin - (int)gc->clip_mask_height - 2,
                  0);
  } else {
    glDisable (dpy->gl_texture_target);
  }
  glActiveTexture (GL_TEXTURE0);
}


static void
set_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color)
{
  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, dpy->main_window, d);
  jwxyz_gl_set_gc (dpy, gc);

  unsigned int depth;

  if (gc) {
    depth = gc->depth;

    switch (gc->gcv.function) {
      case GXset:   color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
      case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
    }
  } else {
    depth = visual_depth (NULL, NULL);
  }

  set_color (dpy, color, depth, gc ? gc->gcv.alpha_allowed_p : False);
}

/* Pushes a GC context; sets color to the foreground color.
 */
static void
set_fg_gc (Display *dpy, Drawable d, GC gc)
{
  set_color_gc (dpy, d, gc, gc->gcv.foreground);
}

static void
next_point(short *v, XPoint p, int mode)
{
  switch (mode) {
    case CoordModeOrigin:
      v[0] = p.x;
      v[1] = p.y;
      break;
    case CoordModePrevious:
      v[0] += p.x;
      v[1] += p.y;
      break;
    default:
      Assert (False, "next_point: bad mode");
      break;
  }
}

static int
DrawPoints (Display *dpy, Drawable d, GC gc,
            XPoint *points, int count, int mode)
{
  short v[2] = {0, 0};

  // TODO: XPoints can be fed directly to OpenGL.
  GLshort *gl_points = enqueue (dpy, d, gc, GL_POINTS, count,
                                gc->gcv.foreground); // TODO: enqueue returns NULL.
  for (unsigned i = 0; i < count; i++) {
    next_point (v, points[i], mode);
    gl_points[2 * i] = v[0];
    gl_points[2 * i + 1] = v[1];
  }

  return 0;
}


static GLint
texture_internalformat (Display *dpy)
{
#ifdef HAVE_JWZGLES
  return dpy->pixel_format;
#else
  return GL_RGBA;
#endif
}

static GLenum
gl_pixel_type (const Display *dpy)
{
  return dpy->pixel_type;
}

static void
clear_texture (Display *dpy)
{
  glTexImage2D (dpy->gl_texture_target, 0, texture_internalformat(dpy), 0, 0,
                0, dpy->pixel_format, gl_pixel_type (dpy), NULL);
}


static void
vertex_pointer (Display *dpy, GLenum type, GLsizei stride,
                const void *pointer)
{
  glVertexPointer(2, type, stride, pointer);
  if (dpy->gc_clip_mask) {
    glClientActiveTexture (GL_TEXTURE1);
    glEnableClientState (GL_TEXTURE_COORD_ARRAY);
    glTexCoordPointer (2, type, stride, pointer);
    glClientActiveTexture (GL_TEXTURE0);
  }
}


void
jwxyz_gl_flush (Display *dpy)
{
  Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");

  if (!dpy->queue_size)
    return;

  // jwxyz_bind_drawable() and jwxyz_gl_set_gc() is called in enqueue().

  glEnableClientState (GL_COLOR_ARRAY);
  glEnableClientState (GL_VERTEX_ARRAY);
  glDisableClientState (GL_TEXTURE_COORD_ARRAY);

  // TODO: Use glColor instead of glColorPointer if there's just one color.
  // TODO: Does OpenGL use both GL_COLOR_ARRAY and glColor at the same time?
  //       (Probably not.)
  glColor4f (1, 1, 1, 1);

  Bool shifted = dpy->queue_mode == GL_POINTS || dpy->queue_mode == GL_LINES;
  if (shifted) {
    glMatrixMode (GL_MODELVIEW);
    glTranslatef (0.5, 0.5, 0);
  }

  glColorPointer (4, GL_UNSIGNED_BYTE, 0, dpy->queue_color);
  vertex_pointer (dpy,
                  dpy->queue_mode == GL_TRIANGLE_STRIP ? GL_FLOAT : GL_SHORT,
                  0, dpy->queue_vertex);
  glDrawArrays (dpy->queue_mode, 0, dpy->queue_size);

  // TODO: This is right, right?
  if (dpy->queue_mode == GL_LINES && dpy->queue_line_cap) {
    Assert (!(dpy->queue_size % 2), "bad count for GL_LINES");
    glColorPointer (4, GL_UNSIGNED_BYTE, sizeof(GLubyte) * 8,
                    dpy->queue_color);
    vertex_pointer (dpy, GL_SHORT, sizeof(GLshort) * 4,
                    (GLshort *)dpy->queue_vertex + 2);
    glDrawArrays (GL_POINTS, 0, dpy->queue_size / 2);
  }

  if (shifted)
    glLoadIdentity ();

  glDisableClientState (GL_COLOR_ARRAY);
  glDisableClientState (GL_VERTEX_ARRAY);

  dpy->queue_size = 0;
}


void
jwxyz_gl_copy_area_read_tex_image (Display *dpy, unsigned src_height,
                                   int src_x, int src_y,
                                   unsigned int width, unsigned int height,
                                   int dst_x, int dst_y)
{
#  if defined HAVE_COCOA && !defined HAVE_IPHONE
  /* TODO: Does this help? */
  /* glFinish(); */
#  endif

  /* TODO: Fix TestX11 + mode_preserve with this one. */

  unsigned tex_w = width, tex_h = height;
  if (!dpy->gl_texture_npot_p) {
    tex_w = to_pow2(tex_w);
    tex_h = to_pow2(tex_h);
  }

  GLint internalformat = texture_internalformat(dpy);

  /* TODO: This probably shouldn't always be texture_rgba. */
  glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);

  if (tex_w == width && tex_h == height) {
    glCopyTexImage2D (dpy->gl_texture_target, 0, internalformat,
                      src_x, src_height - src_y - height, width, height, 0);
  } else {
    glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
                  0, dpy->pixel_format, gl_pixel_type(dpy), NULL);
    glCopyTexSubImage2D (dpy->gl_texture_target, 0, 0, 0,
                         src_x, src_height - src_y - height, width, height);
  }
}

void
jwxyz_gl_copy_area_write_tex_image (Display *dpy, GC gc,
                                    int src_x, int src_y, int src_depth,
                                    unsigned int width, unsigned int height,
                                    int dst_x, int dst_y)
{
  jwxyz_gl_set_gc (dpy, gc);

  /* TODO: Copy-pasted from read_tex_image. */
  unsigned tex_w = width, tex_h = height;
  if (!dpy->gl_texture_npot_p) {
    tex_w = to_pow2(tex_w);
    tex_h = to_pow2(tex_h);
  }

  /* Must match what's in jwxyz_gl_copy_area_read_tex_image. */
  glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);

  jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
                       0, 0, src_depth, width, height, dst_x, dst_y, False);

  clear_texture (dpy);
}


void
jwxyz_gl_draw_image (Display *dpy, GC gc, GLenum gl_texture_target,
                     unsigned int tex_w, unsigned int tex_h,
                     int src_x, int src_y, int src_depth,
                     unsigned int width, unsigned int height,
                     int dst_x, int dst_y, Bool flip_y)
{
  if (!gc || src_depth == gc->depth) {
    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  } else {
    Assert (src_depth == 1 && gc->depth == 32,
            "jwxyz_gl_draw_image: bad depths");

    set_color (dpy, gc->gcv.background, gc->depth, gc->gcv.alpha_allowed_p);

    GLfloat rgba[4];
    query_color (dpy, gc->gcv.foreground, gc->depth, gc->gcv.alpha_allowed_p,
                 rgba);
    glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
    glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, rgba);
  }

  glEnable (gl_texture_target);
  glEnableClientState (GL_TEXTURE_COORD_ARRAY);
  glEnableClientState (GL_VERTEX_ARRAY);

  Assert (!glIsEnabled (GL_COLOR_ARRAY), "glIsEnabled (GL_COLOR_ARRAY)");
  Assert (!glIsEnabled (GL_NORMAL_ARRAY), "glIsEnabled (GL_NORMAL_ARRAY)");

  /* TODO: EXT_draw_texture or whatever it's called. */
  GLfloat vertices[4][2] = {
    {dst_x, dst_y},
    {dst_x, dst_y + height},
    {dst_x + width, dst_y + height},
    {dst_x + width, dst_y}
  };

  GLfloat
    tex_x0 = src_x, tex_y0 = src_y,
    tex_x1 = src_x + width, tex_y1 = src_y;

  if (flip_y)
    tex_y1 += height;
  else
    tex_y0 += height;

# ifndef HAVE_JWZGLES
  if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
# endif
  {
    GLfloat mx = 1.0f / tex_w, my = 1.0f / tex_h;
    tex_x0 *= mx;
    tex_y0 *= my;
    tex_x1 *= mx;
    tex_y1 *= my;
  }

  GLfloat tex_coords[4][2] = {
    {tex_x0, tex_y0},
    {tex_x0, tex_y1},
    {tex_x1, tex_y1},
    {tex_x1, tex_y0}
  };

  vertex_pointer (dpy, GL_FLOAT, 0, vertices);
  glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
  glDrawArrays (GL_TRIANGLE_FAN, 0, 4);

//clear_texture();
  glDisable (gl_texture_target);
}

#if 0
void
jwxyz_gl_copy_area_read_pixels (Display *dpy, Drawable src, Drawable dst,
                                GC gc, int src_x, int src_y,
                                unsigned int width, unsigned int height,
                                int dst_x, int dst_y)
{
  XImage *img = XGetImage (dpy, src, src_x, src_y, width, height, ~0, ZPixmap);
  XPutImage (dpy, dst, gc, img, 0, 0, dst_x, dst_y, width, height);
  XDestroyImage (img);
}
#endif


static int
DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
           int mode)
{
  set_fg_gc (dpy, d, gc);

  /* TODO: Thick lines
   * Zero-length line segments
   * Paths with zero length total (Remember line width, cap style.)
   * Closed loops
   */
  
  if (!count)
    return 0;

  GLshort *vertices = malloc(2 * sizeof(GLshort) * count); // TODO malloc NULL sigh
  
  glMatrixMode (GL_MODELVIEW);
  glTranslatef (0.5f, 0.5f, 0);
  
  short p[2] = {0, 0};
  for (unsigned i = 0; i < count; i++) {
    next_point (p, points[i], mode);
    vertices[2 * i] = p[0];
    vertices[2 * i + 1] = p[1];
  }
  
  glEnableClientState (GL_VERTEX_ARRAY);
  glDisableClientState (GL_TEXTURE_COORD_ARRAY);
  vertex_pointer (dpy, GL_SHORT, 0, vertices);
  glDrawArrays (GL_LINE_STRIP, 0, count);
  
  free (vertices);

  if (gc->gcv.cap_style != CapNotLast) {
    // TODO: How does this look with multisampling?
    // TODO: Disable me for closed loops.
    vertex_pointer (dpy, GL_SHORT, 0, p);
    glDrawArrays (GL_POINTS, 0, 1);
  }

  glLoadIdentity ();
  
  return 0;
}


// Turn line segment into parallelogram based on line_width
//
// TODO: Fix epicycle hack with large thickness, and truchet line segment ends
//
static void
drawThickLine (Display *dpy, Drawable d, GC gc, int line_width,
               XSegment *segments)
{
    double dx, dy, di, m, angle;
    int sx1, sx2, sy1, sy2;

    sx1 = segments->x1;
    sy1 = segments->y1;
    sx2 = segments->x2;
    sy2 = segments->y2;

    dx = sx1 - sx2;
    dy = sy1 - sy2;
    di = sqrt(dx * dx + dy * dy);
    dx = dx / di;
    dy = dy / di;
    m = dy / dx;

    angle = atan(m); 

    float sn = sin(angle);
    float cs = cos(angle);
    float line_width_f = (float) line_width;

    float wsn = line_width_f * (sn/2);
    float csn = line_width_f * (cs/2);

    float x3 = sx1 - wsn;
    float y3 = sy1 + csn;
    float x4 = sx1 + wsn;
    float y4 = sy1 - csn;

    float x5 = sx2 - wsn;
    float y5 = sy2 + csn;
    float x6 = sx2 + wsn;
    float y6 = sy2 - csn;

    GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4,
                               gc->gcv.foreground);
    coords[0] = x3;
    coords[1] = y3;
    coords[2] = x4;
    coords[3] = y4;
    coords[4] = x5;
    coords[5] = y5;
    coords[6] = x6;
    coords[7] = y6;
    finish_triangle_strip (dpy, coords);
}


static int
DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
{
  /* TODO: Caps on thick lines. */
  /* Thin lines <= 1px are offset by +0.5; thick lines are not. */

  if (count == 1 && gc->gcv.line_width > 1) {
    drawThickLine (dpy, d, gc, gc->gcv.line_width, segments);
  }
  else {
    if (dpy->queue_line_cap != (gc->gcv.cap_style != CapNotLast))
      jwxyz_gl_flush (dpy);
    dpy->queue_line_cap = gc->gcv.cap_style != CapNotLast;

    // TODO: Static assert here.
    Assert (sizeof(XSegment) == sizeof(short) * 4 &&
            sizeof(GLshort) == sizeof(short) &&
            offsetof(XSegment, x1) == 0 &&
            offsetof(XSegment, x2) == 4,
            "XDrawSegments: Data alignment mix-up.");

    memcpy (enqueue(dpy, d, gc, GL_LINES, count * 2, gc->gcv.foreground),
            segments, count * sizeof(XSegment));
  }

  return 0;
}


static int
ClearWindow (Display *dpy, Window win)
{
  Assert (win == dpy->main_window, "not a window");

  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, win, win);

  GLfloat color[4];
  JWXYZ_QUERY_COLOR (dpy, dpy->window_background, 1.0f, color);

  glClearColor (color[0], color[1], color[2], 1);
  glClear (GL_COLOR_BUFFER_BIT);
  return True;
}

static unsigned long *
window_background (Display *dpy)
{
  return &dpy->window_background;
}

static void
fill_rects (Display *dpy, Drawable d, GC gc,
            const XRectangle *rectangles, unsigned long nrectangles,
            unsigned long pixel)
{
  for (unsigned long i = 0; i != nrectangles; ++i) {
    const XRectangle *r = &rectangles[i];
    GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4, pixel);
    coords[0] = r->x;
    coords[1] = r->y;
    coords[2] = r->x;
    coords[3] = r->y + r->height;
    coords[4] = r->x + r->width;
    coords[5] = r->y;
    coords[6] = r->x + r->width;
    coords[7] = r->y + r->height;
    finish_triangle_strip (dpy, coords);
  }
}


static int
FillPolygon (Display *dpy, Drawable d, GC gc,
             XPoint *points, int npoints, int shape, int mode)
{
  set_fg_gc(dpy, d, gc);
  
  // TODO: Re-implement the GLU tesselation functions.

  /* Complex: Pedal, and for some reason Attraction, Mountain, Qix, SpeedMine, Starfish
   * Nonconvex: Goop, Pacman, Rocks, Speedmine
   *
   * We currently do Nonconvex with the simple-to-implement ear clipping
   * algorithm, but in the future we can replace that with an algorithm
   * with slower big-O growth
   *
   */
  
  
  // TODO: Feed vertices straight to OpenGL for CoordModeOrigin.

  if (shape == Convex) {

    GLshort *vertices = malloc(npoints * sizeof(GLshort) * 2); // TODO: Oh look, another unchecked malloc.
    short v[2] = {0, 0};
  
    for (unsigned i = 0; i < npoints; i++) {
      next_point(v, points[i], mode);
      vertices[2 * i] = v[0];
      vertices[2 * i + 1] = v[1];
    }

    glEnableClientState (GL_VERTEX_ARRAY);
    glDisableClientState (GL_TEXTURE_COORD_ARRAY);

    vertex_pointer (dpy, GL_SHORT, 0, vertices);
    glDrawArrays (GL_TRIANGLE_FAN, 0, npoints);

    free(vertices);

  } else if (shape == Nonconvex) {

    // TODO: assert that x,y of first and last point match, as that is assumed

    linked_point *root;
    root = (linked_point *) malloc( sizeof(linked_point) );
    set_points_list(points,npoints,root);
    traverse_points_list(dpy, root);

  } else {
    Assert((shape == Convex || shape == Nonconvex),
           "XFillPolygon: (TODO) Unimplemented shape");
  }

  return 0;
}

#define radians(DEG) ((DEG) * M_PI / 180.0)
#define degrees(RAD) ((RAD) * 180.0 / M_PI)

static void
arc_xy (GLfloat *p, GLfloat cx, GLfloat cy, GLfloat w2, GLfloat h2,
        GLfloat theta)
{
  p[0] = cx + cosf(theta) * w2;
  p[1] = cy - sinf(theta) * h2;
}

static void
arc_xy2 (GLfloat *p, GLfloat cx, GLfloat cy, GLfloat w2, GLfloat h2,
         GLfloat theta, GLfloat gglw)
{
  // The inner/outer contour of the stroke of an ellipse is not itself an
  // ellipse.

  GLfloat ct = cosf(theta), st = sinf(theta);

  GLfloat w2st = w2 * st, h2ct = h2 * ct;
  GLfloat w2ct = w2 * ct, h2st = h2 * st;

  GLfloat d = gglw / sqrtf(w2st * w2st + h2ct * h2ct);
  GLfloat dh2ct = d * h2ct, dw2st = d * w2st;

  p[0] = cx +  w2ct + dh2ct;
  p[1] = cy - (h2st + dw2st);
  p[2] = cx +  w2ct - dh2ct;
  p[3] = cy - (h2st - dw2st);
}

static unsigned
mod_neg(int a, unsigned b)
{
  /* Normal modulus is implementation defined for negative numbers. This is 
   * well-defined such that the repeating pattern for a >= 0 is maintained for 
   * a < 0. */
  return a < 0 ? (b - 1) - (-(a + 1) % b) : a % b;
}

static int
draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
          unsigned int width, unsigned int height,
          int angle1, int angle2, Bool fill_p)
{
  /* Let's say the number of line segments needed to make a convincing circle
     is 4*sqrt(radius). (But these arcs aren't necessarily circular arcs...)
   */

  GLfloat w2 = width * 0.5f, h2 = height * 0.5f;
  GLfloat a, b; /* Semi-major/minor axes. */
  if(w2 > h2) {
    a = w2;
    b = h2;
  } else {
    a = h2;
    b = w2;
  }

  const GLfloat tau = 2 * M_PI;

  GLfloat amb = a - b, apb = a + b;
  GLfloat h = (amb * amb) / (apb * apb);
  GLfloat D_approx = apb * (1 + 3 * h / (10 + sqrtf(4 - 3 * h)));
  // From Ramanujan, "Modular Equations and Approximations to π".
  // double C_approx = D_approx * M_PI;
  GLfloat segments_f = sqrtf(8 * D_approx);

  // TODO: Explain how drawing works what with the points of overlapping arcs
  // matching up.

  unsigned segments_360 = segments_f;

  /* TODO: angle2 == 0. This is a tilted square with CapSquare. */
  /* TODO: color, thick lines, CapNotLast for thin lines */
  /* TODO: Transformations. */

  GLfloat segment_angle = tau / segments_360;

  const unsigned deg64 = 360 * 64;
  const GLfloat rad_from_deg64 = tau / deg64;

  if (angle2 < 0) {
    angle1 += angle2;
    angle2 = -angle2;
  }

  angle1 = mod_neg(angle1, deg64); // TODO: Is this OK? Consider negative numbers.

  if (angle2 > deg64)
    angle2 = deg64; // TODO: Handle circles special.

  GLfloat
    angle1_f = angle1 * rad_from_deg64,
    angle2_f = angle2 * rad_from_deg64;

  if (angle2_f > tau) // TODO: Move this up.
    angle2_f = tau;

  GLfloat segment0_angle_part = fmodf(angle1_f, segment_angle);

  unsigned segment0 = ((angle1_f - segment0_angle_part) / segment_angle) + 1.5;

  GLfloat angle_2r = angle2_f - segment0_angle_part;
  unsigned segments = angle_2r / segment_angle;

  GLfloat cx = x + w2, cy = y + h2;

  /* TODO: It would probably be better for vertices at the corners of the
     elliptical sector/arc to be the intersection between a ray extending from
     the ellipse center along one of the specified angles and one of the line
     segments following the outline of the 360-degree ellipse, rather than
     what it is now.
   */

  if (fill_p) {
    GLfloat *data = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, segments + 5,
                             gc->gcv.foreground);
    GLfloat *data_ptr = data;

    unsigned segment1 = segment0 + segments / 2;
    unsigned ds = segment1 - segment0 + 1;
    unsigned ds2 = ds / 2;

    if (ds & 1) {
      arc_xy (data_ptr, cx, cy, w2, h2, (segment0 + ds2) * segment_angle);
      data_ptr += 2;
    }

    unsigned s = ds2;
    while (s) {
      --s;
      arc_xy (data_ptr, cx, cy, w2, h2, (segment0 + s) * segment_angle);
      arc_xy (data_ptr + 2, cx, cy, w2, h2,
              (segment1 - s) * segment_angle);
      data_ptr += 4;
    }

    arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
    data_ptr[2] = cx;
    data_ptr[3] = cy;
    data_ptr[4] = cx;
    data_ptr[5] = cy;
    arc_xy (data_ptr + 6, cx, cy, w2, h2, angle1_f + angle2_f);
    data_ptr += 8;

    unsigned segment2 = segment0 + segments;
    ds = segment2 - segment1;
    ds2 = ds / 2;

    for (s = 0; s != ds2; ++s) {
      arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + s) * segment_angle);
      arc_xy (data_ptr + 2, cx, cy, w2, h2,
              (segment2 - s - 1) * segment_angle);
      data_ptr += 4;
    }

    if (ds & 1) {
      arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + ds2) * segment_angle);
      data_ptr += 2;
    }

    finish_triangle_strip (dpy, data);
  } else if (!gc->gcv.line_width) {
    set_fg_gc(dpy, d, gc);

    GLfloat *data = malloc((segments + 2) * sizeof(GLfloat) * 2); // TODO: Check result.
    GLfloat *data_ptr = data;

    arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
    data_ptr += 2;

    for (unsigned s = 0; s != segments; ++s) {
      // TODO: Make sure values of theta for the following arc_xy call are between
      // angle1_f and angle1_f + angle2_f.
      arc_xy (data_ptr, cx, cy, w2, h2, (segment0 + s) * segment_angle);
      data_ptr += 2;
    }

    arc_xy (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f);
    data_ptr += 2;

    glDisableClientState (GL_TEXTURE_COORD_ARRAY);
    glEnableClientState (GL_VERTEX_ARRAY);

    vertex_pointer (dpy, GL_FLOAT, 0, data);
    glDrawArrays (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP,
                  0,
                  (GLsizei)((data_ptr - data) / 2));

    free(data);
  } else {
    GLfloat gglw = gc->gcv.line_width * 0.5f;

    GLfloat *data = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 2 * segments + 4,
                             gc->gcv.foreground);
    GLfloat *data_ptr = data;

    arc_xy2 (data_ptr, cx, cy, w2, h2, angle1_f, gglw);
    data_ptr += 4;

    for (unsigned s = 0; s != segments; ++s) {
      arc_xy2 (data_ptr, cx, cy, w2, h2, (segment0 + s) * segment_angle,
               gglw);
      data_ptr += 4;
    }

    arc_xy2 (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f, gglw);
    data_ptr += 4;

    finish_triangle_strip (dpy, data);
  }

  return 0;
}


static XGCValues *
gc_gcv (GC gc)
{
  return &gc->gcv;
}


static unsigned int
gc_depth (GC gc)
{
  return gc->depth;
}


static GC
CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
{
  struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
  gc->depth = jwxyz_drawable_depth (d);

  jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
  XChangeGC (dpy, gc, mask, xgcv);
  return gc;
}


static void
free_clip_mask (Display *dpy, GC gc)
{
  if (gc->gcv.clip_mask) {
    if (dpy->gc_clip_mask == gc->clip_mask) {
      jwxyz_gl_flush (dpy);
      dpy->gc_clip_mask = 0;
    }
    glDeleteTextures (1, &gc->clip_mask);
  }
}


static int
FreeGC (Display *dpy, GC gc)
{
  if (gc->gcv.font)
    XUnloadFont (dpy, gc->gcv.font);

  free_clip_mask (dpy, gc);
  free (gc);
  return 0;
}


static int
PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
          int src_x, int src_y, int dest_x, int dest_y,
          unsigned int w, unsigned int h)
{
  jwxyz_assert_display (dpy);
 
  const XRectangle *wr = jwxyz_frame (d);

  Assert (gc, "no GC");
  Assert ((w < 65535), "improbably large width");
  Assert ((h < 65535), "improbably large height");
  Assert ((src_x  < 65535 && src_x  > -65535), "improbably large src_x");
  Assert ((src_y  < 65535 && src_y  > -65535), "improbably large src_y");
  Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
  Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");

  // Clip width and height to the bounds of the Drawable
  //
  if (dest_x + w > wr->width) {
    if (dest_x > wr->width)
      return 0;
    w = wr->width - dest_x;
  }
  if (dest_y + h > wr->height) {
    if (dest_y > wr->height)
      return 0;
    h = wr->height - dest_y;
  }
  if (w <= 0 || h <= 0)
    return 0;

  // Clip width and height to the bounds of the XImage
  //
  if (src_x + w > ximage->width) {
    if (src_x > ximage->width)
      return 0;
    w = ximage->width - src_x;
  }
  if (src_y + h > ximage->height) {
    if (src_y > ximage->height)
      return 0;
    h = ximage->height - src_y;
  }
  if (w <= 0 || h <= 0)
    return 0;

  /* Assert (d->win */

  if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
    return 0;

  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, dpy->main_window, d);
  jwxyz_gl_set_gc (dpy, gc);

  int bpl = ximage->bytes_per_line;
  int bpp = ximage->bits_per_pixel;

  char *tex_data;
  unsigned src_w;
  GLint tex_internalformat;
  GLenum tex_format, tex_type;
  unsigned tex_index;

  if (bpp == 32) {
    tex_data = ximage->data + src_y * bpl + (src_x * 4);

    jwxyz_assert_display(dpy);
    
    /* There probably won't be any hacks that do this, but... */
    Assert (!(bpl % 4), "XPutImage: bytes_per_line not divisible by four.");
    
    tex_internalformat = texture_internalformat(dpy);
    tex_format = dpy->pixel_format;
    tex_type = gl_pixel_type(dpy);
    tex_index = texture_rgba;

    /* GL_UNPACK_ROW_LENGTH is not allowed to be negative. (sigh) */
# ifndef HAVE_JWZGLES
    src_w = w;
    glPixelStorei (GL_UNPACK_ROW_LENGTH, src_w);
# else
    src_w = bpl / 4;
# endif

    // glPixelStorei (GL_UNPACK_ALIGNMENT, 4); // Probably unnecessary.
  } else {
    Assert (bpp == 1, "expected 1 or 32 bpp");
    Assert ((src_x % 8) == 0,
            "XPutImage with non-byte-aligned 1bpp X offset not implemented");

    const char *src_data = ximage->data + src_y * bpl + (src_x / 8);
    unsigned w8 = (w + 7) / 8;

    src_w = w8 * 8;

    tex_data = malloc(src_w * h);

#if 0
    {
      char s[10240];
      int x, y, o;
      Log("#PI ---------- %d %d %08lx %08lx",
          jwxyz_drawable_depth(d), ximage->depth,
          (unsigned long)d, (unsigned long)ximage);
      for (y = 0; y < ximage->height; y++) {
        o = 0;
        for (x = 0; x < ximage->width; x++) {
          unsigned long b = XGetPixel(ximage, x, y);
          s[o++] = (   (b & 0xFF000000)
                    ? ((b & 0x00FFFFFF) ? '#' : '-')
                    : ((b & 0x00FFFFFF) ? '+' : ' '));
        }
        s[o] = 0;
        Log("#PI [%s]",s);
      }
      Log("# PI ----------");
    }
#endif
    uint32_t *data_out = (uint32_t *)tex_data;
    for(unsigned y = h; y; --y) {
      for(unsigned x = 0; x != w8; ++x) {
        // TODO: Does big endian work here?
        uint8_t byte = src_data[x];
        uint32_t word = byte;
        word = (word & 0x3) | ((word & 0xc) << 14);
        word = (word & 0x00010001) | ((word & 0x00020002) << 7);
        data_out[x << 1] = (word << 8) - word;

        word = byte >> 4;
        word = (word & 0x3) | ((word & 0xc) << 14);
        word = (word & 0x00010001) | ((word & 0x00020002) << 7);
        data_out[(x << 1) | 1] = (word << 8) - word;
      }
      src_data += bpl;
      data_out += src_w / 4;
    }
#if 0
    {
      char s[10240];
      int x, y, o;
      Log("#P2 ----------");
      for (y = 0; y < ximage->height; y++) {
        o = 0;
        for (x = 0; x < ximage->width; x++) {
          unsigned long b = ((uint8_t *)tex_data)[y * w + x];
          s[o++] = (   (b & 0xFF000000)
                    ? ((b & 0x00FFFFFF) ? '#' : '-')
                    : ((b & 0x00FFFFFF) ? '+' : ' '));
        }
        s[o] = 0;
        Log("#P2 [%s]",s);
      }
      Log("# P2 ----------");
    }
#endif

    tex_internalformat = GL_LUMINANCE;
    tex_format = GL_LUMINANCE;
    tex_type = GL_UNSIGNED_BYTE;
    tex_index = texture_mono;

    // glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
  }

# if 1 // defined HAVE_JWZGLES
  // Regular OpenGL uses GL_TEXTURE_RECTANGLE_EXT in place of GL_TEXTURE_2D.
  // TODO: Make use of OES_draw_texture.

  unsigned tex_w = src_w, tex_h = h;
  glBindTexture (dpy->gl_texture_target, dpy->textures[tex_index]);

  // A fun project: reimplement xshm.c by means of a PBO using
  // GL_MAP_UNSYNCHRONIZED_BIT.

  tex_image (dpy, tex_internalformat, &tex_w, &tex_h, tex_format, tex_type,
             tex_data);

  if (bpp == 1)
    free(tex_data);

  jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
                       0, 0, bpp, w, h, dest_x, dest_y, True);
# else
  glRasterPos2i (dest_x, dest_y);
  glPixelZoom (1, -1);
  jwxyz_assert_display (dpy);
  glDrawPixels (w, h, dpy->pixel_format, gl_pixel_type(dpy), data);
# endif

  jwxyz_assert_gl ();

  return 0;
}

/* At the moment only XGetImage and get_xshm_image use XGetSubImage. */
/* #### Twang calls XGetImage on the window intending to get a
   buffer full of black.  This is returning a buffer full of white
   instead of black for some reason. */
static XImage *
GetSubImage (Display *dpy, Drawable d, int x, int y,
             unsigned int width, unsigned int height,
             unsigned long plane_mask, int format,
             XImage *dest_image, int dest_x, int dest_y)
{
  Assert ((width  < 65535), "improbably large width");
  Assert ((height < 65535), "improbably large height");
  Assert ((x < 65535 && x > -65535), "improbably large x");
  Assert ((y < 65535 && y > -65535), "improbably large y");

  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, dpy->main_window, d);
  
  // TODO: What if this reads off the edge? What is supposed to happen?

  {
    // In case the caller tries to write off the edge.
    int
      max_width = dest_image->width - dest_x,
      max_height = dest_image->height - dest_y;

    if (width > max_width) {
      width = max_width;
    }
    
    if (height > max_height) {
      height = max_height;
    }
  }
  
  Assert (jwxyz_drawable_depth (d) == dest_image->depth, "XGetSubImage: depth mismatch");
  
  if (dest_image->depth == visual_depth (NULL, NULL)) {
    Assert (!(dest_image->bytes_per_line % 4), "XGetSubImage: bytes_per_line not divisible by 4");
    unsigned pixels_per_line = dest_image->bytes_per_line / 4;
#ifdef HAVE_JWZGLES
    Assert (pixels_per_line == width, "XGetSubImage: (TODO) pixels_per_line != width");
#else
    glPixelStorei (GL_PACK_ROW_LENGTH, pixels_per_line);
#endif
    glPixelStorei (GL_PACK_ALIGNMENT, 4);
    
    uint32_t *dest_data = (uint32_t *)dest_image->data + pixels_per_line * dest_y + dest_x;
    
    glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
                  dpy->pixel_format, gl_pixel_type(dpy), dest_data);

    /* Flip this upside down. :( */
    uint32_t *top = dest_data;
    uint32_t *bottom = dest_data + pixels_per_line * (height - 1);
    for (unsigned y = height / 2; y; --y) {
      for (unsigned x = 0; x != width; ++x) {
        uint32_t px = top[x];
        top[x] = bottom[x];
        bottom[x] = px;
      }
      top += pixels_per_line;
      bottom -= pixels_per_line;
    }
  } else {

    uint32_t *rgba_image = malloc(width * height * 4);
    Assert (rgba_image, "not enough memory");

    // Must be GL_RGBA; GL_RED isn't available.
    glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
                  GL_RGBA, GL_UNSIGNED_BYTE, rgba_image);

    Assert (!(dest_x % 8), "XGetSubImage: dest_x not byte-aligned");
    uint8_t *top =
      (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y
      + dest_x / 8;
#if 0
    {
      char s[10240];
      Log("#GI ---------- %d %d  %d x %d %08lx", 
          jwxyz_drawable_depth(d), dest_image->depth,
          width, height,
          (unsigned long) d);
      for (int y = 0; y < height; y++) {
        int x;
        for (x = 0; x < width; x++) {
          unsigned long b = rgba_image[(height-y-1) * width + x];
          s[x] = (   (b & 0xFF000000)
                  ? ((b & 0x00FFFFFF) ? '#' : '-')
                  : ((b & 0x00FFFFFF) ? '+' : ' '));
        }
        s[x] = 0;
        Log("#GI [%s]",s);
      }
      Log("#GI ----------");
    }
#endif
    const uint32_t *bottom = rgba_image + width * (height - 1);
    for (unsigned y = height; y; --y) {
      memset (top, 0, width / 8);
      for (unsigned x = 0; x != width; ++x) {
        if (bottom[x] & 0x80)
          top[x >> 3] |= (1 << (x & 7));
      }
      top += dest_image->bytes_per_line;
      bottom -= width;
    }

    free (rgba_image);
  }

  return dest_image;
}


static int
SetClipMask (Display *dpy, GC gc, Pixmap m)
{
  Assert (m != dpy->main_window, "not a pixmap");

  free_clip_mask (dpy, gc);

  if (!m) {
    gc->clip_mask = 0;
    return 0;
  }

  Assert (jwxyz_drawable_depth (m) == 1, "wrong depth for clip mask");

  const XRectangle *frame = jwxyz_frame (m);
  gc->clip_mask_width = frame->width;
  gc->clip_mask_height = frame->height;

  /* Apparently GL_ALPHA isn't a valid format for a texture used in an FBO:

    (86) Are any one- or two- component formats color-renderable?

            Presently none of the one- or two- component texture formats
            defined in unextended OpenGL is color-renderable.  The R
            and RG float formats defined by the NV_float_buffer
            extension are color-renderable.

            Although an early draft of the FBO specification permitted
            rendering into alpha, luminance, and intensity formats, this
            this capability was pulled when it was realized that it was
            under-specified exactly how rendering into these formats
            would work.  (specifically, how R/G/B/A map to I/L/A)

     <https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_framebuffer_object.txt>

     ...Therefore, 1-bit drawables get to have wasted color channels.
     Currently, R=G=B=grey, and the alpha channel is unused.
   */

  /* There doesn't seem to be any way to move what's on one of the color
     channels to the alpha channel in GLES 1.1 without leaving the GPU.
   */

  /* GLES 1.1 only supports GL_CLAMP_TO_EDGE or GL_REPEAT for
     GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, so to prevent drawing outside
     the clip mask pixmap, there's two options:
     1. Use glScissor.
     2. Add a black border.

     Option #2 is in use here.

     The following converts in-place an RGBA image to alpha-only image with a
     1px black border.
   */

  // Prefix/suffix: 1 row+1 pixel, rounded to nearest int32.
  size_t pad0 = frame->width + 3, pad = (pad0 + 3) & ~3;
  uint8_t *data = malloc(2 * pad + frame->width * frame->height * 4);

  uint8_t *rgba = data + pad;
  uint8_t *alpha = rgba;
  uint8_t *alpha0 = alpha - pad0;
  memset (alpha0, 0, pad0); // Top row.

  jwxyz_gl_flush (dpy);
  jwxyz_bind_drawable (dpy, dpy->main_window, m);
  glReadPixels (0, 0, frame->width, frame->height, GL_RGBA, GL_UNSIGNED_BYTE,
                rgba);

  for (unsigned y = 0; y != frame->height; ++y) {
    for (unsigned x = 0; x != frame->width; ++x)
      alpha[x] = rgba[x * 4];

    rgba += frame->width * 4;

    alpha += frame->width;
    alpha[0] = 0;
    alpha[1] = 0;
    alpha += 2;
  }

  alpha -= 2;
  memset (alpha, 0, pad0); // Bottom row.

  glGenTextures (1, &gc->clip_mask);
  tex_parameters (dpy, gc->clip_mask);

  glPixelStorei (GL_UNPACK_ALIGNMENT, 1);

  unsigned tex_w = frame->width + 2, tex_h = frame->height + 2;
  tex_image (dpy, GL_ALPHA, &tex_w, &tex_h, GL_ALPHA, GL_UNSIGNED_BYTE,
             alpha0);

  free(data);

  return 0;
}

static int
SetClipOrigin (Display *dpy, GC gc, int x, int y)
{
  gc->gcv.clip_x_origin = x;
  gc->gcv.clip_y_origin = y;
  return 0;
}

void set_points_list(XPoint *points, int npoints, linked_point *root)
{
    linked_point *current;  

    current = root;
    for (int i = 0; i < npoints - 2 ; i++) {
        current->x = points[i].x;
        current->y = points[i].y;
        current->next = (linked_point *) malloc(sizeof(linked_point)); 
        current = current->next;
    }
    current->x = points[npoints-2].x;
    current->y = points[npoints-2].y;
    current->next = root;
}


double compute_edge_length(linked_point * a, linked_point * b)
{

    int xdiff, ydiff, xsq, ysq, added;
    double xy_add, edge_length;

    xdiff = a->x - b->x;
    ydiff = a->y - b->y;
    xsq = xdiff * xdiff;
    ysq = ydiff * ydiff;
    added = xsq + ysq;
    xy_add = (double) added;
    edge_length = sqrt(xy_add);
    return edge_length;
}

double get_angle(double a, double b, double c)
{
    double cos_a, i_cos_a;
    cos_a = (((b * b) + (c * c)) - (a * a)) / (double) (2.0 * b * c);
    i_cos_a = acos(cos_a);
    return i_cos_a;
}


Bool is_same_slope(linked_point * a)
{

    int abx, bcx, aby, bcy, aa, bb;
    linked_point *b;
    linked_point *c;

    b = a->next;
    c = b->next;

    // test if slopes are indefinite for both line segments
    if (a->x == b->x) {
        return b->x == c->x;
    } else if (b->x == c->x) {
        return False;   // false, as ax/bx is not indefinite
    }

    abx = a->x - b->x;
    bcx = b->x - c->x;
    aby = a->y - b->y;
    bcy = b->y - c->y;
    aa = abx * bcy;
    bb = bcx * aby;

    return aa == bb;
}

void draw_three_vertices(Display *dpy, linked_point * a, Bool triangle)
{

    linked_point *b;
    linked_point *c;
    GLenum drawType;

    b = a->next;
    c = b->next;

    GLfloat vertices[3][2] = {
        {a->x, a->y},
        {b->x, b->y},
        {c->x, c->y}
    };

    if (triangle) {
        drawType = GL_TRIANGLES;
    } else {
        drawType = GL_LINES;
    }

    glEnableClientState(GL_VERTEX_ARRAY);
    vertex_pointer(dpy, GL_FLOAT, 0, vertices);
    glDrawArrays(drawType, 0, 3);

    free(b);  // cut midpoint off from remaining polygon vertex list
    a->next = c;
}


Bool is_an_ear(linked_point * a)
{
    double edge_ab, edge_bc, edge_ac;
    double angle_a, angle_b, angle_c;
    double my_pi;
    linked_point *b, *c;

    b = a->next;
    c = b->next;
    my_pi = (double) M_PI;

    edge_ab = compute_edge_length(a, b);
    edge_bc = compute_edge_length(b, c);
    edge_ac = compute_edge_length(a, c);
    angle_a = get_angle(edge_bc, edge_ab, edge_ac);
    angle_b = get_angle(edge_ac, edge_ab, edge_bc);
    angle_c = get_angle(edge_ab, edge_ac, edge_bc);

    return angle_a < my_pi && angle_b < my_pi && angle_c < my_pi;
}


Bool is_three_point_loop(linked_point * head)
{
    return head->x == head->next->next->next->x
        && head->y == head->next->next->next->y;
}


void traverse_points_list(Display *dpy, linked_point * root)
{
    linked_point *head;
    head = root;

    while (!is_three_point_loop(head)) {
        if (is_an_ear(head)) {
            draw_three_vertices(dpy, head, True);
        } else if (is_same_slope(head)) {
            draw_three_vertices(dpy, head, False);
        } else {
            head = head->next;
        }
    }

    // handle final three vertices in polygon
    if (is_an_ear(head)) {
        draw_three_vertices(dpy, head, True);
    } else if (is_same_slope(head)) {
        draw_three_vertices(dpy, head, False);
    } else {
        free(head->next->next);
        free(head->next);
        free(head);
        Assert (False, "traverse_points_list: unknown configuration");
    }

    free(head->next);
    free(head);
}


const struct jwxyz_vtbl gl_vtbl = {
  root,
  visual,
  display_sources_data,

  window_background,
  draw_arc,
  fill_rects,
  gc_gcv,
  gc_depth,
  jwxyz_draw_string,

  jwxyz_gl_copy_area,

  DrawPoints,
  DrawSegments,
  CreateGC,
  FreeGC,
  ClearWindow,
  SetClipMask,
  SetClipOrigin,
  FillPolygon,
  DrawLines,
  PutImage,
  GetSubImage
};

#endif /* JWXYZ_GL -- entire file */
