summaryrefslogblamecommitdiffstats
path: root/jwxyz/jwxyz-gl.c
blob: ed4c9c94bae873f8c6a5d862cf6e9883c85ae9f8 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
                                                                     











                                                                              

                                                                          
 

                                                                     

   
 

























































                                                                              
                   












                                   


                         






























































































                                                                                             
                                                  




































































































































































































































































































































































                                                                                                  
                                                                    



















































































































































































































































































































































































                                                                                   
                                                




























                                                                            

                                                                        















                                                                            
                                                                            





































































































































































































































































































































                                                                                                          

                                                                   
 






















                                                                         










                                                                               




                                                        


                                                                             
 

                                                






               
 







                                                                  


                                                                             
 
                                     
 



                                                                  
                                             

                                  

                                             








                                                                                  

         

                                       
 







                                                                               
                                               
 

                                   






                                                                              
               





















































                                                                                        
                  








                                                                                  
                  
 

                                                  
 

























                                                                             
   
 





















































































































































































































































































































































































































































































































































































































































































                                                                                                  
/* 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 */