summaryrefslogtreecommitdiffstats
path: root/jwxyz/jwxyz-gl.c
diff options
context:
space:
mode:
Diffstat (limited to 'jwxyz/jwxyz-gl.c')
-rw-r--r--jwxyz/jwxyz-gl.c2125
1 files changed, 2125 insertions, 0 deletions
diff --git a/jwxyz/jwxyz-gl.c b/jwxyz/jwxyz-gl.c
new file mode 100644
index 0000000..369d16f
--- /dev/null
+++ b/jwxyz/jwxyz-gl.c
@@ -0,0 +1,2125 @@
+/* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ */
+
+/* JWXYZ Is Not Xlib.
+
+ But it's a bunch of function definitions that bear some resemblance to
+ Xlib and that do OpenGL-ish things that bear some resemblance to the
+ things that Xlib might have done.
+
+ This is the version of jwxyz for Android. The version used by macOS
+ and iOS is in jwxyz.m.
+ */
+
+/* Be advised, this is all very much a work in progress. */
+
+/* There is probably no reason to ever implement indexed-color rendering here,
+ even if many screenhacks still work with PseudoColor.
+ - OpenGL ES 1.1 (Android, iOS) doesn't support indexed color.
+ - macOS only provides indexed color via AGL (now deprecated), not
+ NSOpenGLPixelFormat.
+ */
+
+/* TODO:
+ - malloc error checking
+ - Check max texture sizes for XGet/PutImage, XCopyArea.
+ - Optional 5:5:5 16-bit color
+*/
+
+/* Techniques & notes:
+ - iOS: OpenGL ES 2.0 isn't always available. Use OpenGL ES 1.1.
+ - OS X: Drivers can go back to OpenGL 1.1 (GeForce 2 MX on 10.5.8).
+ - Use stencil buffers (OpenGL 1.0+) for bitmap clipping masks.
+ - Pixmaps can be any of the following, depending on GL implementation.
+ - This requires offscreen rendering. Fortunately, this is always
+ available.
+ - OS X: Drawable objects, including: pixel buffers and offscreen
+ memory.
+ - Offscreen buffers w/ CGLSetOffScreen (10.0+)
+ - http://lists.apple.com/archives/mac-opengl/2002/Jun/msg00087.html
+ provides a very ugly solution for hardware-accelerated offscreen
+ rendering with CGLSetParameter(*, kCGLCPSurfaceTexture, *) on OS X
+ 10.1+. Apple docs say it's actually for OS X 10.3+, instead.
+ - Pixel buffers w/ CGLSetPBuffer (10.3+, now deprecated)
+ - Requires APPLE_pixel_buffer.
+ - Available in software on x86 only.
+ - Always available on hardware.
+ - Need to blit? Use OpenGL pixel buffers. (GL_PIXEL_PACK_BUFFER)
+ - Framebuffer objects w/ GL_(ARB|EXT)_framebuffer_object
+ - TODO: Can EXT_framebuffers be different sizes?
+ - Preferred on 10.7+
+ - iOS: Use OES_framebuffer_object, it's always present.
+ */
+
+/* OpenGL hacks call a number of X11 functions, including
+ XCopyArea, XDrawString, XGetImage
+ XCreatePixmap, XCreateGC, XCreateImage
+ XPutPixel
+ Check these, of course.
+ */
+
+#ifdef JWXYZ_GL /* entire file */
+
+#include <math.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <wchar.h>
+
+#ifdef HAVE_COCOA
+# ifdef USE_IPHONE
+# import <QuartzCore/QuartzCore.h>
+# include <OpenGLES/ES1/gl.h>
+# include <OpenGLES/ES1/glext.h>
+# else
+# include <OpenGL/glu.h>
+# endif
+#else
+/* TODO: Does this work on iOS? */
+# ifndef HAVE_JWZGLES
+# include <gl/glu.h>
+# else
+# include <GLES/gl.h>
+# include <GLES/glext.h>
+# endif
+#endif
+
+#ifdef HAVE_JWZGLES
+# include "jwzglesI.h"
+#endif
+
+#include "jwxyzI.h"
+#include "jwxyz-timers.h"
+#include "yarandom.h"
+#include "utf8wc.h"
+#include "xft.h"
+#include "pow2.h"
+
+#define countof(x) (sizeof((x))/sizeof((*x)))
+
+union color_bytes {
+ uint32_t pixel;
+ uint8_t bytes[4];
+};
+
+// Use two textures: one for RGBA, one for luminance. Older Android doesn't
+// seem to like it when textures change format.
+enum {
+ texture_rgba,
+ texture_mono
+};
+
+struct jwxyz_Display {
+ const struct jwxyz_vtbl *vtbl; // Must come first.
+
+ Window main_window;
+ GLenum pixel_format, pixel_type;
+ Visual visual;
+ struct jwxyz_sources_data *timers_data;
+
+ Bool gl_texture_npot_p;
+ /* Bool opengl_core_p */;
+ GLenum gl_texture_target;
+
+ GLuint textures[2]; // Also can work on the desktop.
+
+ unsigned long window_background;
+
+ int gc_function;
+ Bool gc_alpha_allowed_p;
+ int gc_clip_x_origin, gc_clip_y_origin;
+ GLuint gc_clip_mask;
+
+ // Alternately, there could be one queue per pixmap.
+ size_t queue_size, queue_capacity;
+ Drawable queue_drawable;
+ GLint queue_mode;
+ void *queue_vertex;
+ uint32_t *queue_color;
+ Bool queue_line_cap;
+};
+
+struct jwxyz_GC {
+ XGCValues gcv;
+ unsigned int depth;
+ GLuint clip_mask;
+ unsigned clip_mask_width, clip_mask_height;
+};
+
+struct jwxyz_linked_point {
+ short x, y;
+ linked_point *next;
+};
+
+
+void
+jwxyz_assert_display(Display *dpy)
+{
+ if(!dpy)
+ return;
+ jwxyz_assert_gl ();
+ jwxyz_assert_drawable (dpy->main_window, dpy->main_window);
+}
+
+
+void
+jwxyz_set_matrices (Display *dpy, unsigned width, unsigned height,
+ Bool window_p)
+{
+ Assert (width, "no width");
+ Assert (height, "no height");
+
+ /* TODO: Check registration pattern from Interference with rectangles instead of points. */
+
+ // The projection matrix is always set as follows. The modelview matrix is
+ // usually identity, but for points and thin lines, it's translated by 0.5.
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+# if defined(USE_IPHONE) || defined(HAVE_ANDROID)
+
+ if (window_p && ignore_rotation_p(dpy)) {
+ int o = (int) current_device_rotation();
+ glRotatef (-o, 0, 0, 1);
+ }
+
+ // glPointSize(1); // This is the default.
+
+#ifdef HAVE_JWZGLES
+ glOrthof /* TODO: Is glOrthox worth it? Signs point to no. */
+#else
+ glOrtho
+#endif
+ (0, width, height, 0, -1, 1);
+
+ glMatrixMode(GL_MODELVIEW);
+# endif // HAVE_MOBILE
+}
+
+#ifndef HAVE_JWZGLES
+
+struct gl_version
+{
+ // iOS always uses OpenGL ES 1.1.
+ unsigned major;
+ unsigned minor;
+};
+
+static GLboolean
+gl_check_ver (const struct gl_version *caps,
+ unsigned gl_major,
+ unsigned gl_minor)
+{
+ return caps->major > gl_major ||
+ (caps->major == gl_major && caps->minor >= gl_minor);
+}
+
+#endif
+
+
+static void
+tex_parameters (Display *d, GLuint texture)
+{
+ // TODO: Check for (ARB|EXT|NV)_texture_rectangle. (All three are alike.)
+ // Rectangle textures should be present on OS X with the following exceptions:
+ // - Generic renderer on PowerPC OS X 10.4 and earlier
+ // - ATI Rage 128
+ glBindTexture (d->gl_texture_target, texture);
+ // TODO: This is all somewhere else. Refactor.
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+
+ // This might be redundant for rectangular textures.
+# ifndef HAVE_JWZGLES
+ const GLint wrap = GL_CLAMP;
+# else // HAVE_JWZGLES
+ const GLint wrap = GL_CLAMP_TO_EDGE;
+# endif // HAVE_JWZGLES
+
+ // In OpenGL, CLAMP_TO_EDGE is OpenGL 1.2 or GL_SGIS_texture_edge_clamp.
+ // This is always present with OpenGL ES.
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_S, wrap);
+ glTexParameteri (d->gl_texture_target, GL_TEXTURE_WRAP_T, wrap);
+}
+
+static void
+tex_size (Display *dpy, unsigned *tex_w, unsigned *tex_h)
+{
+ if (!dpy->gl_texture_npot_p) {
+ *tex_w = to_pow2(*tex_w);
+ *tex_h = to_pow2(*tex_h);
+ }
+}
+
+static void
+tex_image (Display *dpy, GLenum internalformat,
+ unsigned *tex_w, unsigned *tex_h, GLenum format, GLenum type,
+ const void *data)
+{
+ unsigned w = *tex_w, h = *tex_h;
+ tex_size (dpy, tex_w, tex_h);
+
+ // TODO: Would using glTexSubImage2D exclusively be faster?
+ if (*tex_w == w && *tex_h == h) {
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
+ 0, format, type, data);
+ } else {
+ // TODO: Sampling the last row might be a problem if src_x != 0.
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, *tex_w, *tex_h,
+ 0, format, type, NULL);
+ glTexSubImage2D (dpy->gl_texture_target, 0, 0, 0, w, h,
+ format, type, data);
+ }
+}
+
+
+extern const struct jwxyz_vtbl gl_vtbl;
+
+Display *
+jwxyz_gl_make_display (Window w)
+{
+ Display *d = (Display *) calloc (1, sizeof(*d));
+ d->vtbl = &gl_vtbl;
+
+# ifndef HAVE_JWZGLES
+ struct gl_version version;
+
+ {
+ const GLubyte *version_str = glGetString (GL_VERSION);
+
+ /* iPhone is always OpenGL ES 1.1. */
+ if (sscanf ((const char *) version_str, "%u.%u",
+ &version.major, &version.minor) < 2)
+ {
+ version.major = 1;
+ version.minor = 1;
+ }
+ }
+# endif // !HAVE_JWZGLES
+
+ const GLubyte *extensions = glGetString (GL_EXTENSIONS);
+
+ /* See:
+ - Apple TN2080: Understanding and Detecting OpenGL Functionality.
+ - OpenGL Programming Guide for the Mac - Best Practices for Working with
+ Texture Data - Optimal Data Formats and Types
+ */
+
+ // If a video adapter suports BGRA textures, then that's probably as fast as
+ // you're gonna get for getting a texture onto the screen.
+# ifdef HAVE_JWZGLES
+ /* TODO: Make BGRA work on iOS. As it is, it breaks XPutImage. (glTexImage2D, AFAIK) */
+ d->pixel_format = GL_RGBA; /*
+ gluCheckExtension ((const GLubyte *) "GL_APPLE_texture_format_BGRA8888",
+ extensions) ? GL_BGRA_EXT : GL_RGBA; */
+ d->pixel_type = GL_UNSIGNED_BYTE;
+ // See also OES_read_format.
+# else // !HAVE_JWZGLES
+ if (gl_check_ver (&version, 1, 2) ||
+ (gluCheckExtension ((const GLubyte *) "GL_EXT_bgra", extensions) &&
+ gluCheckExtension ((const GLubyte *) "GL_APPLE_packed_pixels",
+ extensions))) {
+ // APPLE_packed_pixels is only ever available on iOS, never Android.
+ d->pixel_format = GL_BGRA_EXT;
+ // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
+ d->pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ } else {
+ d->pixel_format = GL_RGBA;
+ d->pixel_type = GL_UNSIGNED_BYTE;
+ }
+ // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
+ // sense on PowerPC.
+# endif // !HAVE_JWZGLES
+
+ // On really old systems, it would make sense to split textures
+ // into subsections, to work around the maximum texture size.
+# ifndef HAVE_JWZGLES
+ d->gl_texture_npot_p = gluCheckExtension ((const GLubyte *)
+ "GL_ARB_texture_rectangle",
+ extensions);
+ d->gl_texture_target = d->gl_texture_npot_p ?
+ GL_TEXTURE_RECTANGLE_EXT :
+ GL_TEXTURE_2D;
+# else
+ d->gl_texture_npot_p = jwzgles_gluCheckExtension
+ ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions) ||
+ jwzgles_gluCheckExtension
+ ((const GLubyte *) "GL_OES_texture_npot", extensions) ||
+ jwzgles_gluCheckExtension // From PixelFlinger 1.4
+ ((const GLubyte *) "GL_ARB_texture_non_power_of_two", extensions);
+
+ d->gl_texture_target = GL_TEXTURE_2D;
+# endif
+
+ Visual *v = &d->visual;
+ v->class = TrueColor;
+ if (d->pixel_format == GL_BGRA_EXT) {
+ v->red_mask = 0x00ff0000;
+ v->green_mask = 0x0000ff00;
+ v->blue_mask = 0x000000ff;
+ v->alpha_mask = 0xff000000;
+ } else {
+ Assert(d->pixel_format == GL_RGBA,
+ "jwxyz_gl_make_display: Unknown pixel_format");
+ unsigned long masks[4];
+ for (unsigned i = 0; i != 4; ++i) {
+ union color_bytes color;
+ color.pixel = 0;
+ color.bytes[i] = 0xff;
+ masks[i] = color.pixel;
+ }
+ v->red_mask = masks[0];
+ v->green_mask = masks[1];
+ v->blue_mask = masks[2];
+ v->alpha_mask = masks[3];
+ }
+
+ d->timers_data = jwxyz_sources_init (XtDisplayToApplicationContext (d));
+
+ d->window_background = BlackPixel(d,0);
+
+ d->main_window = w;
+
+ {
+ GLint max_texture_size, max_texture_units;
+ glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size);
+ glGetIntegerv (GL_MAX_TEXTURE_UNITS, &max_texture_units);
+ Log ("GL_MAX_TEXTURE_SIZE: %d, GL_MAX_TEXTURE_UNITS: %d\n",
+ max_texture_size, max_texture_units);
+
+ // OpenGL ES 1.1 and OpenGL 1.3 both promise at least 2 texture units:
+ // OpenGL (R) ES Common/Common-Lite Profile Specification, Version 1.1.12 (Full Specification)
+ // https://www.khronos.org/registry/OpenGL/specs/es/1.1/es_full_spec_1.1.pdf
+ // * Table 6.22. Implementation Dependent Values
+ // * D.2 Enhanced Texture Processing
+ // (OpenGL 1.2 provides multitexturing as an ARB extension, and requires 1
+ // texture unit only.)
+
+ // ...but the glGet reference page contradicts this, and says there can be
+ // just one.
+ // https://www.khronos.org/registry/OpenGL-Refpages/es1.1/xhtml/glGet.xml
+ }
+
+ glGenTextures (countof (d->textures), d->textures);
+
+ for (unsigned i = 0; i != countof (d->textures); i++) {
+ tex_parameters (d, d->textures[i]);
+ }
+
+ d->gc_function = GXcopy;
+ d->gc_alpha_allowed_p = False;
+ d->gc_clip_mask = 0;
+
+ jwxyz_assert_display(d);
+ return d;
+}
+
+void
+jwxyz_gl_free_display (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ /* TODO: Go over everything. */
+
+ free (dpy->queue_vertex);
+ free (dpy->queue_color);
+
+ jwxyz_sources_free (dpy->timers_data);
+
+ free (dpy);
+}
+
+
+/* Call this when the View changes size or position.
+ */
+void
+jwxyz_window_resized (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ const XRectangle *new_frame = jwxyz_frame (dpy->main_window);
+ unsigned new_width = new_frame->width;
+ unsigned new_height = new_frame->height;
+
+ Assert (new_width, "jwxyz_window_resized: No width.");
+ Assert (new_height, "jwxyz_window_resized: No height.");
+
+/*if (cgc) w->cgc = cgc;
+ Assert (w->cgc, "no CGContext"); */
+
+ Log("resize: %d, %d\n", new_width, new_height);
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, dpy->main_window);
+
+ // TODO: What does the iPhone need?
+
+ // iOS only: If the main_window is not the current_drawable, then set_matrices
+ // was already called in bind_drawable.
+ jwxyz_set_matrices (dpy, new_width, new_height, True);
+
+/*
+ GLint draw_buffer;
+ glGetIntegerv (GL_DRAW_BUFFER, &draw_buffer);
+
+ glDrawBuffer (GL_FRONT);
+ glClearColor (1, 0, 1, 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+ glDrawBuffer (GL_BACK);
+ glClearColor (0, 1, 1, 0);
+ glClear (GL_COLOR_BUFFER_BIT);
+
+ glDrawBuffer (draw_buffer); */
+
+ // Stylish and attractive purple!
+ // glClearColor (1, 0, 1, 0.5);
+ // glClear (GL_COLOR_BUFFER_BIT);
+}
+
+
+static jwxyz_sources_data *
+display_sources_data (Display *dpy)
+{
+ return dpy->timers_data;
+}
+
+
+static Window
+root (Display *dpy)
+{
+ return dpy->main_window;
+}
+
+static Visual *
+visual (Display *dpy)
+{
+ return &dpy->visual;
+}
+
+
+/* GC attributes by usage and OpenGL implementation:
+
+ All drawing functions:
+ function | glLogicOp w/ GL_COLOR_LOGIC_OP
+ clip_x_origin, clip_y_origin, clip_mask | Multitexturing w/ GL_TEXTURE1
+
+ Shape drawing functions:
+ foreground, background | glColor*
+
+ XDrawLines, XDrawRectangles, XDrawSegments:
+ line_width, cap_style, join_style | Lotsa vertices
+
+ XFillPolygon:
+ fill_rule | Multiple GL_TRIANGLE_FANs
+
+ XDrawText:
+ font | Cocoa, then OpenGL display lists.
+
+ alpha_allowed_p | GL_BLEND
+
+ antialias_p | Well, there's options:
+ * Multisampling would work, but that's something that would need to be set
+ per-Pixmap, not per-GC.
+ * GL_POINT, LINE, and POLYGON_SMOOTH are the old-school way of doing
+ this, but POINT_SMOOTH is unnecessary, and POLYGON_SMOOTH is missing from
+ GLES 1. All three are missing from GLES 2. Word on the street is that
+ these are deprecated anyway.
+ * Tiny textures with bilinear filtering to get the same effect as LINE_ and
+ POLYGON_SMOOTH. A bit tricky.
+ * Do nothing. Android hardware is very often high-DPI enough that
+ anti-aliasing doesn't matter all that much.
+
+ Nothing, really:
+ subwindow_mode
+ */
+
+static void *
+enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count,
+ unsigned long pixel)
+{
+ if (dpy->queue_size &&
+ (!gc || /* Could allow NULL GCs here... */
+ dpy->gc_function != gc->gcv.function ||
+ dpy->gc_alpha_allowed_p != gc->gcv.alpha_allowed_p ||
+ dpy->gc_clip_mask != gc->clip_mask ||
+ dpy->gc_clip_x_origin != gc->gcv.clip_x_origin ||
+ dpy->gc_clip_y_origin != gc->gcv.clip_y_origin ||
+ dpy->queue_mode != mode ||
+ dpy->queue_drawable != d)) {
+ jwxyz_gl_flush (dpy);
+ }
+
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ if (mode == GL_TRIANGLE_STRIP)
+ Assert (count, "empty triangle strip");
+ // Use degenerate triangles to cut down on draw calls.
+ Bool prepend2 = mode == GL_TRIANGLE_STRIP && dpy->queue_size;
+
+ // ####: Primitive restarts should be used here when (if) they're available.
+ if (prepend2)
+ count += 2;
+
+ // TODO: Use glColor when we can get away with it.
+ size_t old_size = dpy->queue_size;
+ dpy->queue_size += count;
+ if (dpy->queue_size > dpy->queue_capacity) {
+ dpy->queue_capacity = dpy->queue_size * 2;
+
+ uint32_t *new_color = realloc (
+ dpy->queue_color, sizeof(*dpy->queue_color) * dpy->queue_capacity);
+ /* Allocate vertices as if they were always GLfloats. Otherwise, if
+ queue_vertex is allocated to hold GLshorts, then things get switched
+ to GLfloats, queue_vertex would be too small for the given capacity.
+ */
+ GLshort *new_vertex = realloc (
+ dpy->queue_vertex, sizeof(GLfloat) * 2 * dpy->queue_capacity);
+
+ if (!new_color || !new_vertex)
+ return NULL;
+
+ dpy->queue_color = new_color;
+ dpy->queue_vertex = new_vertex;
+ }
+
+ dpy->queue_mode = mode;
+ dpy->queue_drawable = d;
+
+ union color_bytes color;
+
+ // Like query_color, but for bytes.
+ jwxyz_validate_pixel (dpy, pixel, jwxyz_drawable_depth (d),
+ gc ? gc->gcv.alpha_allowed_p : False);
+
+ if (jwxyz_drawable_depth (d) == 1) {
+ uint8_t b = pixel ? 0xff : 0;
+ color.bytes[0] = b;
+ color.bytes[1] = b;
+ color.bytes[2] = b;
+ color.bytes[3] = 0xff;
+ } else {
+ JWXYZ_QUERY_COLOR (dpy, pixel, 0xffull, color.bytes);
+ }
+
+ for (size_t i = 0; i != count; ++i) // TODO: wmemset when applicable.
+ dpy->queue_color[i + old_size] = color.pixel;
+
+ void *result = (char *)dpy->queue_vertex + old_size * 2 *
+ (mode == GL_TRIANGLE_STRIP ? sizeof(GLfloat) : sizeof(GLshort));
+ if (prepend2) {
+ dpy->queue_color[old_size] = dpy->queue_color[old_size - 1];
+ result = (GLfloat *)result + 4;
+ }
+ return result;
+}
+
+
+static void
+finish_triangle_strip (Display *dpy, GLfloat *enqueue_result)
+{
+ if (enqueue_result != dpy->queue_vertex) {
+ enqueue_result[-4] = enqueue_result[-6];
+ enqueue_result[-3] = enqueue_result[-5];
+ enqueue_result[-2] = enqueue_result[0];
+ enqueue_result[-1] = enqueue_result[1];
+ }
+}
+
+
+static void
+query_color (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p, GLfloat *rgba)
+{
+ jwxyz_validate_pixel (dpy, pixel, depth, alpha_allowed_p);
+
+ if (depth == 1) {
+ GLfloat f = pixel;
+ rgba[0] = f;
+ rgba[1] = f;
+ rgba[2] = f;
+ rgba[3] = 1;
+ } else {
+ JWXYZ_QUERY_COLOR (dpy, pixel, 1.0f, rgba);
+ }
+}
+
+
+static void
+set_color (Display *dpy, unsigned long pixel, unsigned int depth,
+ Bool alpha_allowed_p)
+{
+ GLfloat rgba[4];
+ query_color (dpy, pixel, depth, alpha_allowed_p, rgba);
+ glColor4f (rgba[0], rgba[1], rgba[2], rgba[3]);
+}
+
+/* Pushes a GC context; sets Function and ClipMask. */
+void
+jwxyz_gl_set_gc (Display *dpy, GC gc)
+{
+ int function;
+ Bool alpha_allowed_p;
+ GLuint clip_mask;
+
+ // GC is NULL for XClearArea and XClearWindow.
+ if (gc) {
+ function = gc->gcv.function;
+ alpha_allowed_p = gc->gcv.alpha_allowed_p || gc->clip_mask;
+ clip_mask = gc->clip_mask;
+ } else {
+ function = GXcopy;
+ alpha_allowed_p = False;
+ clip_mask = 0;
+ }
+
+ /* GL_COLOR_LOGIC_OP: OpenGL 1.1. */
+ if (function != dpy->gc_function) {
+ dpy->gc_function = function;
+ if (function != GXcopy) {
+ /* Fun fact: The glLogicOp opcode constants are the same as the X11 GX*
+ function constants | GL_CLEAR.
+ */
+ glEnable (GL_COLOR_LOGIC_OP);
+ glLogicOp (gc->gcv.function | GL_CLEAR);
+ } else {
+ glDisable (GL_COLOR_LOGIC_OP);
+ }
+ }
+
+ /* Cocoa uses add/subtract/difference blending in place of logical ops.
+ It looks nice, but implementing difference blending in OpenGL appears to
+ require GL_KHR_blend_equation_advanced, and support for this is not
+ widespread.
+ */
+
+ dpy->gc_alpha_allowed_p = alpha_allowed_p;
+ if (alpha_allowed_p || clip_mask) {
+ // TODO: Maybe move glBlendFunc to XCreatePixmap?
+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ glEnable (GL_BLEND);
+ } else {
+ glDisable (GL_BLEND);
+ }
+
+ /* Texture units:
+ GL_TEXTURE0: Texture for XPutImage/XCopyArea (if applicable)
+ GL_TEXTURE1: Texture for clip masks (if applicable)
+ */
+ dpy->gc_clip_mask = clip_mask;
+
+ glActiveTexture (GL_TEXTURE1);
+ if (clip_mask) {
+ glEnable (dpy->gl_texture_target);
+ glBindTexture (dpy->gl_texture_target, gc->clip_mask);
+
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
+ alpha_allowed_p ? GL_MODULATE : GL_REPLACE);
+
+ glMatrixMode (GL_TEXTURE);
+ glLoadIdentity ();
+
+ unsigned
+ tex_w = gc->clip_mask_width + 2, tex_h = gc->clip_mask_height + 2;
+ tex_size (dpy, &tex_w, &tex_h);
+
+# ifndef HAVE_JWZGLES
+ if (dpy->gl_texture_target == GL_TEXTURE_RECTANGLE_EXT)
+ {
+ glScalef (1, -1, 1);
+ }
+ else
+# endif
+ {
+ glScalef (1.0f / tex_w, -1.0f / tex_h, 1);
+ }
+
+ glTranslatef (1 - gc->gcv.clip_x_origin,
+ 1 - gc->gcv.clip_y_origin - (int)gc->clip_mask_height - 2,
+ 0);
+ } else {
+ glDisable (dpy->gl_texture_target);
+ }
+ glActiveTexture (GL_TEXTURE0);
+}
+
+
+static void
+set_color_gc (Display *dpy, Drawable d, GC gc, unsigned long color)
+{
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ unsigned int depth;
+
+ if (gc) {
+ depth = gc->depth;
+
+ switch (gc->gcv.function) {
+ case GXset: color = (depth == 1 ? 1 : WhitePixel(dpy,0)); break;
+ case GXclear: color = (depth == 1 ? 0 : BlackPixel(dpy,0)); break;
+ }
+ } else {
+ depth = visual_depth (NULL, NULL);
+ }
+
+ set_color (dpy, color, depth, gc ? gc->gcv.alpha_allowed_p : False);
+}
+
+/* Pushes a GC context; sets color to the foreground color.
+ */
+static void
+set_fg_gc (Display *dpy, Drawable d, GC gc)
+{
+ set_color_gc (dpy, d, gc, gc->gcv.foreground);
+}
+
+static void
+next_point(short *v, XPoint p, int mode)
+{
+ switch (mode) {
+ case CoordModeOrigin:
+ v[0] = p.x;
+ v[1] = p.y;
+ break;
+ case CoordModePrevious:
+ v[0] += p.x;
+ v[1] += p.y;
+ break;
+ default:
+ Assert (False, "next_point: bad mode");
+ break;
+ }
+}
+
+static int
+DrawPoints (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int count, int mode)
+{
+ short v[2] = {0, 0};
+
+ // TODO: XPoints can be fed directly to OpenGL.
+ GLshort *gl_points = enqueue (dpy, d, gc, GL_POINTS, count,
+ gc->gcv.foreground); // TODO: enqueue returns NULL.
+ for (unsigned i = 0; i < count; i++) {
+ next_point (v, points[i], mode);
+ gl_points[2 * i] = v[0];
+ gl_points[2 * i + 1] = v[1];
+ }
+
+ return 0;
+}
+
+
+static GLint
+texture_internalformat (Display *dpy)
+{
+#ifdef HAVE_JWZGLES
+ return dpy->pixel_format;
+#else
+ return GL_RGBA;
+#endif
+}
+
+static GLenum
+gl_pixel_type (const Display *dpy)
+{
+ return dpy->pixel_type;
+}
+
+static void
+clear_texture (Display *dpy)
+{
+ glTexImage2D (dpy->gl_texture_target, 0, texture_internalformat(dpy), 0, 0,
+ 0, dpy->pixel_format, gl_pixel_type (dpy), NULL);
+}
+
+
+static void
+vertex_pointer (Display *dpy, GLenum type, GLsizei stride,
+ const void *pointer)
+{
+ glVertexPointer(2, type, stride, pointer);
+ if (dpy->gc_clip_mask) {
+ glClientActiveTexture (GL_TEXTURE1);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ glTexCoordPointer (2, type, stride, pointer);
+ glClientActiveTexture (GL_TEXTURE0);
+ }
+}
+
+
+void
+jwxyz_gl_flush (Display *dpy)
+{
+ Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable");
+
+ if (!dpy->queue_size)
+ return;
+
+ // jwxyz_bind_drawable() and jwxyz_gl_set_gc() is called in enqueue().
+
+ glEnableClientState (GL_COLOR_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ // TODO: Use glColor instead of glColorPointer if there's just one color.
+ // TODO: Does OpenGL use both GL_COLOR_ARRAY and glColor at the same time?
+ // (Probably not.)
+ glColor4f (1, 1, 1, 1);
+
+ Bool shifted = dpy->queue_mode == GL_POINTS || dpy->queue_mode == GL_LINES;
+ if (shifted) {
+ glMatrixMode (GL_MODELVIEW);
+ glTranslatef (0.5, 0.5, 0);
+ }
+
+ glColorPointer (4, GL_UNSIGNED_BYTE, 0, dpy->queue_color);
+ vertex_pointer (dpy,
+ dpy->queue_mode == GL_TRIANGLE_STRIP ? GL_FLOAT : GL_SHORT,
+ 0, dpy->queue_vertex);
+ glDrawArrays (dpy->queue_mode, 0, dpy->queue_size);
+
+ // TODO: This is right, right?
+ if (dpy->queue_mode == GL_LINES && dpy->queue_line_cap) {
+ Assert (!(dpy->queue_size % 2), "bad count for GL_LINES");
+ glColorPointer (4, GL_UNSIGNED_BYTE, sizeof(GLubyte) * 8,
+ dpy->queue_color);
+ vertex_pointer (dpy, GL_SHORT, sizeof(GLshort) * 4,
+ (GLshort *)dpy->queue_vertex + 2);
+ glDrawArrays (GL_POINTS, 0, dpy->queue_size / 2);
+ }
+
+ if (shifted)
+ glLoadIdentity ();
+
+ glDisableClientState (GL_COLOR_ARRAY);
+ glDisableClientState (GL_VERTEX_ARRAY);
+
+ dpy->queue_size = 0;
+}
+
+
+void
+jwxyz_gl_copy_area_read_tex_image (Display *dpy, unsigned src_height,
+ int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+# if defined HAVE_COCOA && !defined USE_IPHONE
+ /* TODO: Does this help? */
+ /* glFinish(); */
+# endif
+
+ /* TODO: Fix TestX11 + mode_preserve with this one. */
+
+ unsigned tex_w = width, tex_h = height;
+ if (!dpy->gl_texture_npot_p) {
+ tex_w = to_pow2(tex_w);
+ tex_h = to_pow2(tex_h);
+ }
+
+ GLint internalformat = texture_internalformat(dpy);
+
+ /* TODO: This probably shouldn't always be texture_rgba. */
+ glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);
+
+ if (tex_w == width && tex_h == height) {
+ glCopyTexImage2D (dpy->gl_texture_target, 0, internalformat,
+ src_x, src_height - src_y - height, width, height, 0);
+ } else {
+ glTexImage2D (dpy->gl_texture_target, 0, internalformat, tex_w, tex_h,
+ 0, dpy->pixel_format, gl_pixel_type(dpy), NULL);
+ glCopyTexSubImage2D (dpy->gl_texture_target, 0, 0, 0,
+ src_x, src_height - src_y - height, width, height);
+ }
+}
+
+void
+jwxyz_gl_copy_area_write_tex_image (Display *dpy, GC gc, int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+ jwxyz_gl_set_gc (dpy, gc);
+
+ /* TODO: Copy-pasted from read_tex_image. */
+ unsigned tex_w = width, tex_h = height;
+ if (!dpy->gl_texture_npot_p) {
+ tex_w = to_pow2(tex_w);
+ tex_h = to_pow2(tex_h);
+ }
+
+ /* Must match what's in jwxyz_gl_copy_area_read_tex_image. */
+ glBindTexture (dpy->gl_texture_target, dpy->textures[texture_rgba]);
+
+ jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
+ 0, 0, gc->depth, width, height, dst_x, dst_y, False);
+
+ clear_texture (dpy);
+}
+
+
+void
+jwxyz_gl_draw_image (Display *dpy, GC gc, GLenum gl_texture_target,
+ unsigned int tex_w, unsigned int tex_h,
+ int src_x, int src_y, int src_depth,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y, Bool flip_y)
+{
+ if (!gc || src_depth == gc->depth) {
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
+ } else {
+ Assert (src_depth == 1 && gc->depth == 32,
+ "jwxyz_gl_draw_image: bad depths");
+
+ set_color (dpy, gc->gcv.background, gc->depth, gc->gcv.alpha_allowed_p);
+
+ GLfloat rgba[4];
+ query_color (dpy, gc->gcv.foreground, gc->depth, gc->gcv.alpha_allowed_p,
+ rgba);
+ glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
+ glTexEnvfv (GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, rgba);
+ }
+
+ glEnable (gl_texture_target);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+
+ Assert (!glIsEnabled (GL_COLOR_ARRAY), "glIsEnabled (GL_COLOR_ARRAY)");
+ Assert (!glIsEnabled (GL_NORMAL_ARRAY), "glIsEnabled (GL_NORMAL_ARRAY)");
+
+ /* TODO: EXT_draw_texture or whatever it's called. */
+ GLfloat vertices[4][2] = {
+ {dst_x, dst_y},
+ {dst_x, dst_y + height},
+ {dst_x + width, dst_y + height},
+ {dst_x + width, dst_y}
+ };
+
+ GLfloat
+ tex_x0 = src_x, tex_y0 = src_y,
+ tex_x1 = src_x + width, tex_y1 = src_y;
+
+ if (flip_y)
+ tex_y1 += height;
+ else
+ tex_y0 += height;
+
+# ifndef HAVE_JWZGLES
+ if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
+# endif
+ {
+ GLfloat mx = 1.0f / tex_w, my = 1.0f / tex_h;
+ tex_x0 *= mx;
+ tex_y0 *= my;
+ tex_x1 *= mx;
+ tex_y1 *= my;
+ }
+
+ GLfloat tex_coords[4][2] = {
+ {tex_x0, tex_y0},
+ {tex_x0, tex_y1},
+ {tex_x1, tex_y1},
+ {tex_x1, tex_y0}
+ };
+
+ vertex_pointer (dpy, GL_FLOAT, 0, vertices);
+ glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
+
+//clear_texture();
+ glDisable (gl_texture_target);
+}
+
+#if 0
+void
+jwxyz_gl_copy_area_read_pixels (Display *dpy, Drawable src, Drawable dst,
+ GC gc, int src_x, int src_y,
+ unsigned int width, unsigned int height,
+ int dst_x, int dst_y)
+{
+ XImage *img = XGetImage (dpy, src, src_x, src_y, width, height, ~0, ZPixmap);
+ XPutImage (dpy, dst, gc, img, 0, 0, dst_x, dst_y, width, height);
+ XDestroyImage (img);
+}
+#endif
+
+
+static int
+DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count,
+ int mode)
+{
+ set_fg_gc (dpy, d, gc);
+
+ /* TODO: Thick lines
+ * Zero-length line segments
+ * Paths with zero length total (Remember line width, cap style.)
+ * Closed loops
+ */
+
+ if (!count)
+ return 0;
+
+ GLshort *vertices = malloc(2 * sizeof(GLshort) * count); // TODO malloc NULL sigh
+
+ glMatrixMode (GL_MODELVIEW);
+ glTranslatef (0.5f, 0.5f, 0);
+
+ short p[2] = {0, 0};
+ for (unsigned i = 0; i < count; i++) {
+ next_point (p, points[i], mode);
+ vertices[2 * i] = p[0];
+ vertices[2 * i + 1] = p[1];
+ }
+
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ vertex_pointer (dpy, GL_SHORT, 0, vertices);
+ glDrawArrays (GL_LINE_STRIP, 0, count);
+
+ free (vertices);
+
+ if (gc->gcv.cap_style != CapNotLast) {
+ // TODO: How does this look with multisampling?
+ // TODO: Disable me for closed loops.
+ vertex_pointer (dpy, GL_SHORT, 0, p);
+ glDrawArrays (GL_POINTS, 0, 1);
+ }
+
+ glLoadIdentity ();
+
+ return 0;
+}
+
+
+// Turn line segment into parallelogram based on line_width
+//
+// TODO: Fix epicycle hack with large thickness, and truchet line segment ends
+//
+static void
+drawThickLine (Display *dpy, Drawable d, GC gc, int line_width,
+ XSegment *segments)
+{
+ double dx, dy, di, m, angle;
+ int sx1, sx2, sy1, sy2;
+
+ sx1 = segments->x1;
+ sy1 = segments->y1;
+ sx2 = segments->x2;
+ sy2 = segments->y2;
+
+ dx = sx1 - sx2;
+ dy = sy1 - sy2;
+ di = sqrt(dx * dx + dy * dy);
+ dx = dx / di;
+ dy = dy / di;
+ m = dy / dx;
+
+ angle = atan(m);
+
+ float sn = sin(angle);
+ float cs = cos(angle);
+ float line_width_f = (float) line_width;
+
+ float wsn = line_width_f * (sn/2);
+ float csn = line_width_f * (cs/2);
+
+ float x3 = sx1 - wsn;
+ float y3 = sy1 + csn;
+ float x4 = sx1 + wsn;
+ float y4 = sy1 - csn;
+
+ float x5 = sx2 - wsn;
+ float y5 = sy2 + csn;
+ float x6 = sx2 + wsn;
+ float y6 = sy2 - csn;
+
+ GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4,
+ gc->gcv.foreground);
+ coords[0] = x3;
+ coords[1] = y3;
+ coords[2] = x4;
+ coords[3] = y4;
+ coords[4] = x5;
+ coords[5] = y5;
+ coords[6] = x6;
+ coords[7] = y6;
+ finish_triangle_strip (dpy, coords);
+}
+
+
+static int
+DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count)
+{
+ /* TODO: Caps on thick lines. */
+ /* Thin lines <= 1px are offset by +0.5; thick lines are not. */
+
+ if (count == 1 && gc->gcv.line_width > 1) {
+ drawThickLine (dpy, d, gc, gc->gcv.line_width, segments);
+ }
+ else {
+ if (dpy->queue_line_cap != (gc->gcv.cap_style != CapNotLast))
+ jwxyz_gl_flush (dpy);
+ dpy->queue_line_cap = gc->gcv.cap_style != CapNotLast;
+
+ // TODO: Static assert here.
+ Assert (sizeof(XSegment) == sizeof(short) * 4 &&
+ sizeof(GLshort) == sizeof(short) &&
+ offsetof(XSegment, x1) == 0 &&
+ offsetof(XSegment, x2) == 4,
+ "XDrawSegments: Data alignment mix-up.");
+
+ memcpy (enqueue(dpy, d, gc, GL_LINES, count * 2, gc->gcv.foreground),
+ segments, count * sizeof(XSegment));
+ }
+
+ return 0;
+}
+
+
+static int
+ClearWindow (Display *dpy, Window win)
+{
+ Assert (win == dpy->main_window, "not a window");
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, win, win);
+
+ GLfloat color[4];
+ JWXYZ_QUERY_COLOR (dpy, dpy->window_background, 1.0f, color);
+
+ glClearColor (color[0], color[1], color[2], 1);
+ glClear (GL_COLOR_BUFFER_BIT);
+ return True;
+}
+
+static unsigned long *
+window_background (Display *dpy)
+{
+ return &dpy->window_background;
+}
+
+static void
+fill_rects (Display *dpy, Drawable d, GC gc,
+ const XRectangle *rectangles, unsigned long nrectangles,
+ unsigned long pixel)
+{
+ for (unsigned long i = 0; i != nrectangles; ++i) {
+ const XRectangle *r = &rectangles[i];
+ GLfloat *coords = enqueue (dpy, d, gc, GL_TRIANGLE_STRIP, 4, pixel);
+ coords[0] = r->x;
+ coords[1] = r->y;
+ coords[2] = r->x;
+ coords[3] = r->y + r->height;
+ coords[4] = r->x + r->width;
+ coords[5] = r->y;
+ coords[6] = r->x + r->width;
+ coords[7] = r->y + r->height;
+ finish_triangle_strip (dpy, coords);
+ }
+}
+
+
+static int
+FillPolygon (Display *dpy, Drawable d, GC gc,
+ XPoint *points, int npoints, int shape, int mode)
+{
+ set_fg_gc(dpy, d, gc);
+
+ // TODO: Re-implement the GLU tesselation functions.
+
+ /* Complex: Pedal, and for some reason Attraction, Mountain, Qix, SpeedMine, Starfish
+ * Nonconvex: Goop, Pacman, Rocks, Speedmine
+ *
+ * We currently do Nonconvex with the simple-to-implement ear clipping
+ * algorithm, but in the future we can replace that with an algorithm
+ * with slower big-O growth
+ *
+ */
+
+
+ // TODO: Feed vertices straight to OpenGL for CoordModeOrigin.
+
+ if (shape == Convex) {
+
+ GLshort *vertices = malloc(npoints * sizeof(GLshort) * 2); // TODO: Oh look, another unchecked malloc.
+ short v[2] = {0, 0};
+
+ for (unsigned i = 0; i < npoints; i++) {
+ next_point(v, points[i], mode);
+ vertices[2 * i] = v[0];
+ vertices[2 * i + 1] = v[1];
+ }
+
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ vertex_pointer (dpy, GL_SHORT, 0, vertices);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, npoints);
+
+ free(vertices);
+
+ } else if (shape == Nonconvex) {
+
+ // TODO: assert that x,y of first and last point match, as that is assumed
+
+ linked_point *root;
+ root = (linked_point *) malloc( sizeof(linked_point) );
+ set_points_list(points,npoints,root);
+ traverse_points_list(dpy, root);
+
+ } else {
+ Assert((shape == Convex || shape == Nonconvex),
+ "XFillPolygon: (TODO) Unimplemented shape");
+ }
+
+ return 0;
+}
+
+#define radians(DEG) ((DEG) * M_PI / 180.0)
+#define degrees(RAD) ((RAD) * 180.0 / M_PI)
+
+static void
+arc_xy(GLfloat *p, double cx, double cy, double w2, double h2, double theta)
+{
+ p[0] = cos(theta) * w2 + cx;
+ p[1] = -sin(theta) * h2 + cy;
+}
+
+static unsigned
+mod_neg(int a, unsigned b)
+{
+ /* Normal modulus is implementation defined for negative numbers. This is
+ * well-defined such that the repeating pattern for a >= 0 is maintained for
+ * a < 0. */
+ return a < 0 ? (b - 1) - (-(a + 1) % b) : a % b;
+}
+
+/* TODO: Fill in arcs with line width > 1 */
+static int
+draw_arc (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ int gglw = gc->gcv.line_width;
+
+ if (fill_p || gglw <= 1) {
+ draw_arc_gl (dpy, d, gc, x, y, width, height, angle1, angle2, fill_p);
+ }
+ else {
+ int w1, w2, h1, h2, gglwh;
+ w1 = width + gglw;
+ h1 = height + gglw;
+ h2 = height - gglw;
+ w2 = width - gglw;
+ gglwh = gglw / 2;
+ int x1 = x - gglwh;
+ int x2 = x + gglwh;
+ int y1 = y - gglwh;
+ int y2 = y + gglwh;
+ //draw_arc_gl (dpy, d, gc, x, y, width, height, angle1, angle2, fill_p);
+ draw_arc_gl (dpy, d, gc, x1, y1, w1, h1, angle1, angle2, fill_p);
+ draw_arc_gl (dpy, d, gc, x2, y2, w2, h2, angle1, angle2, fill_p);
+ }
+ return 0;
+}
+
+
+int
+draw_arc_gl (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height,
+ int angle1, int angle2, Bool fill_p)
+{
+ set_fg_gc(dpy, d, gc);
+
+ /* Let's say the number of line segments needed to make a convincing circle is
+ 4*sqrt(radius). (But these arcs aren't necessarily circular arcs...) */
+
+ double w2 = width * 0.5f, h2 = height * 0.5f;
+ double a, b; /* Semi-major/minor axes. */
+ if(w2 > h2) {
+ a = w2;
+ b = h2;
+ } else {
+ a = h2;
+ b = w2;
+ }
+
+ const double two_pi = 2 * M_PI;
+
+ double amb = a - b, apb = a + b;
+ double h = (amb * amb) / (apb * apb);
+ // TODO: Math cleanup.
+ double C_approx = M_PI * apb * (1 + 3 * h / (10 + sqrtf(4 - 3 * h)));
+ double segments_f = 4 * sqrtf(C_approx / (2 * M_PI));
+
+ // TODO: Explain how drawing works what with the points of overlapping arcs
+ // matching up.
+
+#if 1
+ unsigned segments_360 = segments_f;
+
+ /* TODO: angle2 == 0. This is a tilted square with CapSquare. */
+ /* TODO: color, thick lines, CapNotLast for thin lines */
+ /* TODO: Transformations. */
+
+ double segment_angle = two_pi / segments_360;
+
+ const unsigned deg64 = 360 * 64;
+ const double rad_from_deg64 = two_pi / deg64;
+
+ if (angle2 < 0) {
+ angle1 += angle2;
+ angle2 = -angle2;
+ }
+
+ angle1 = mod_neg(angle1, deg64); // TODO: Is this OK? Consider negative numbers.
+
+ if (angle2 > deg64)
+ angle2 = deg64; // TODO: Handle circles special.
+
+ double
+ angle1_f = angle1 * rad_from_deg64,
+ angle2_f = angle2 * rad_from_deg64;
+
+ if (angle2_f > two_pi) // TODO: Move this up.
+ angle2_f = two_pi;
+
+ double segment1_angle_part = fmodf(angle1_f, segment_angle);
+
+ unsigned segment1 = ((angle1_f - segment1_angle_part) / segment_angle) + 1.5;
+
+ double angle_2r = angle2_f - segment1_angle_part;
+ unsigned segments = angle_2r / segment_angle;
+
+ GLfloat cx = x + w2, cy = y + h2;
+
+ GLfloat *data = malloc((segments + 3) * sizeof(GLfloat) * 2); // TODO: Check result.
+
+ GLfloat *data_ptr = data;
+ if (fill_p) {
+ data_ptr[0] = cx;
+ data_ptr[1] = cy;
+ data_ptr += 2;
+ }
+
+ arc_xy (data_ptr, cx, cy, w2, h2, angle1_f);
+ data_ptr += 2;
+
+ for (unsigned s = 0; s != segments; ++s) {
+ // TODO: Make sure values of theta for the following arc_xy call are between
+ // angle1_f and angle1_f + angle2_f.
+ arc_xy (data_ptr, cx, cy, w2, h2, (segment1 + s) * segment_angle);
+ data_ptr += 2;
+ }
+
+ arc_xy (data_ptr, cx, cy, w2, h2, angle1_f + angle2_f);
+ data_ptr += 2;
+
+ glDisableClientState (GL_TEXTURE_COORD_ARRAY);
+ glEnableClientState (GL_VERTEX_ARRAY);
+
+ vertex_pointer (dpy, GL_FLOAT, 0, data);
+ glDrawArrays (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP,
+ 0,
+ (GLsizei)((data_ptr - data) / 2));
+
+ free(data);
+
+#endif
+
+#if 0
+ unsigned segments = segments_f * (fabs(angle2) / (360 * 64));
+
+ glBegin (fill_p ? GL_TRIANGLE_FAN : GL_LINE_STRIP);
+
+ if (fill_p /* && gc->gcv.arc_mode == ArcPieSlice */)
+ glVertex2f (cx, cy);
+
+ /* TODO: This should fix the middle points of the arc so that the starting and ending points are OK. */
+
+ float to_radians = 2 * M_PI / (360 * 64);
+ float theta = angle1 * to_radians, d_theta = angle2 * to_radians / segments;
+
+ for (unsigned s = 0; s != segments + 1; ++s) /* TODO: This is the right number of segments, yes? */
+ {
+ glVertex2f(cos(theta) * w2 + cx, -sin(theta) * h2 + cy);
+ theta += d_theta;
+ }
+
+ glEnd ();
+#endif
+
+ return 0;
+}
+
+
+static XGCValues *
+gc_gcv (GC gc)
+{
+ return &gc->gcv;
+}
+
+
+static unsigned int
+gc_depth (GC gc)
+{
+ return gc->depth;
+}
+
+
+static GC
+CreateGC (Display *dpy, Drawable d, unsigned long mask, XGCValues *xgcv)
+{
+ struct jwxyz_GC *gc = (struct jwxyz_GC *) calloc (1, sizeof(*gc));
+ gc->depth = jwxyz_drawable_depth (d);
+
+ jwxyz_gcv_defaults (dpy, &gc->gcv, gc->depth);
+ XChangeGC (dpy, gc, mask, xgcv);
+ return gc;
+}
+
+
+static void
+free_clip_mask (Display *dpy, GC gc)
+{
+ if (gc->gcv.clip_mask) {
+ if (dpy->gc_clip_mask == gc->clip_mask) {
+ jwxyz_gl_flush (dpy);
+ dpy->gc_clip_mask = 0;
+ }
+ glDeleteTextures (1, &gc->clip_mask);
+ }
+}
+
+
+static int
+FreeGC (Display *dpy, GC gc)
+{
+ if (gc->gcv.font)
+ XUnloadFont (dpy, gc->gcv.font);
+
+ free_clip_mask (dpy, gc);
+ free (gc);
+ return 0;
+}
+
+
+static int
+PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h)
+{
+ jwxyz_assert_display (dpy);
+
+ const XRectangle *wr = jwxyz_frame (d);
+
+ Assert (gc, "no GC");
+ Assert ((w < 65535), "improbably large width");
+ Assert ((h < 65535), "improbably large height");
+ Assert ((src_x < 65535 && src_x > -65535), "improbably large src_x");
+ Assert ((src_y < 65535 && src_y > -65535), "improbably large src_y");
+ Assert ((dest_x < 65535 && dest_x > -65535), "improbably large dest_x");
+ Assert ((dest_y < 65535 && dest_y > -65535), "improbably large dest_y");
+
+ // Clip width and height to the bounds of the Drawable
+ //
+ if (dest_x + w > wr->width) {
+ if (dest_x > wr->width)
+ return 0;
+ w = wr->width - dest_x;
+ }
+ if (dest_y + h > wr->height) {
+ if (dest_y > wr->height)
+ return 0;
+ h = wr->height - dest_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ // Clip width and height to the bounds of the XImage
+ //
+ if (src_x + w > ximage->width) {
+ if (src_x > ximage->width)
+ return 0;
+ w = ximage->width - src_x;
+ }
+ if (src_y + h > ximage->height) {
+ if (src_y > ximage->height)
+ return 0;
+ h = ximage->height - src_y;
+ }
+ if (w <= 0 || h <= 0)
+ return 0;
+
+ /* Assert (d->win */
+
+ if (jwxyz_dumb_drawing_mode(dpy, d, gc, dest_x, dest_y, w, h))
+ return 0;
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+ jwxyz_gl_set_gc (dpy, gc);
+
+ int bpl = ximage->bytes_per_line;
+ int bpp = ximage->bits_per_pixel;
+
+ char *tex_data;
+ unsigned src_w;
+ GLint tex_internalformat;
+ GLenum tex_format, tex_type;
+ unsigned tex_index;
+
+ if (bpp == 32) {
+ tex_data = ximage->data + src_y * bpl + (src_x * 4);
+
+ jwxyz_assert_display(dpy);
+
+ /* There probably won't be any hacks that do this, but... */
+ Assert (!(bpl % 4), "XPutImage: bytes_per_line not divisible by four.");
+
+ tex_internalformat = texture_internalformat(dpy);
+ tex_format = dpy->pixel_format;
+ tex_type = gl_pixel_type(dpy);
+ tex_index = texture_rgba;
+
+ /* GL_UNPACK_ROW_LENGTH is not allowed to be negative. (sigh) */
+# ifndef HAVE_JWZGLES
+ src_w = w;
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, src_w);
+# else
+ src_w = bpl / 4;
+# endif
+
+ // glPixelStorei (GL_UNPACK_ALIGNMENT, 4); // Probably unnecessary.
+ } else {
+ Assert (bpp == 1, "expected 1 or 32 bpp");
+ Assert ((src_x % 8) == 0,
+ "XPutImage with non-byte-aligned 1bpp X offset not implemented");
+
+ const char *src_data = ximage->data + src_y * bpl + (src_x / 8);
+ unsigned w8 = (w + 7) / 8;
+
+ src_w = w8 * 8;
+
+ tex_data = malloc(src_w * h);
+
+#if 0
+ {
+ char s[10240];
+ int x, y, o;
+ Log("#PI ---------- %d %d %08lx %08lx",
+ jwxyz_drawable_depth(d), ximage->depth,
+ (unsigned long)d, (unsigned long)ximage);
+ for (y = 0; y < ximage->height; y++) {
+ o = 0;
+ for (x = 0; x < ximage->width; x++) {
+ unsigned long b = XGetPixel(ximage, x, y);
+ s[o++] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[o] = 0;
+ Log("#PI [%s]",s);
+ }
+ Log("# PI ----------");
+ }
+#endif
+ uint32_t *data_out = (uint32_t *)tex_data;
+ for(unsigned y = h; y; --y) {
+ for(unsigned x = 0; x != w8; ++x) {
+ // TODO: Does big endian work here?
+ uint8_t byte = src_data[x];
+ uint32_t word = byte;
+ word = (word & 0x3) | ((word & 0xc) << 14);
+ word = (word & 0x00010001) | ((word & 0x00020002) << 7);
+ data_out[x << 1] = (word << 8) - word;
+
+ word = byte >> 4;
+ word = (word & 0x3) | ((word & 0xc) << 14);
+ word = (word & 0x00010001) | ((word & 0x00020002) << 7);
+ data_out[(x << 1) | 1] = (word << 8) - word;
+ }
+ src_data += bpl;
+ data_out += src_w / 4;
+ }
+#if 0
+ {
+ char s[10240];
+ int x, y, o;
+ Log("#P2 ----------");
+ for (y = 0; y < ximage->height; y++) {
+ o = 0;
+ for (x = 0; x < ximage->width; x++) {
+ unsigned long b = ((uint8_t *)tex_data)[y * w + x];
+ s[o++] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[o] = 0;
+ Log("#P2 [%s]",s);
+ }
+ Log("# P2 ----------");
+ }
+#endif
+
+ tex_internalformat = GL_LUMINANCE;
+ tex_format = GL_LUMINANCE;
+ tex_type = GL_UNSIGNED_BYTE;
+ tex_index = texture_mono;
+
+ // glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+ }
+
+# if 1 // defined HAVE_JWZGLES
+ // Regular OpenGL uses GL_TEXTURE_RECTANGLE_EXT in place of GL_TEXTURE_2D.
+ // TODO: Make use of OES_draw_texture.
+
+ unsigned tex_w = src_w, tex_h = h;
+ glBindTexture (dpy->gl_texture_target, dpy->textures[tex_index]);
+
+ // A fun project: reimplement xshm.c by means of a PBO using
+ // GL_MAP_UNSYNCHRONIZED_BIT.
+
+ tex_image (dpy, tex_internalformat, &tex_w, &tex_h, tex_format, tex_type,
+ tex_data);
+
+ if (bpp == 1)
+ free(tex_data);
+
+ jwxyz_gl_draw_image (dpy, gc, dpy->gl_texture_target, tex_w, tex_h,
+ 0, 0, bpp, w, h, dest_x, dest_y, True);
+# else
+ glRasterPos2i (dest_x, dest_y);
+ glPixelZoom (1, -1);
+ jwxyz_assert_display (dpy);
+ glDrawPixels (w, h, dpy->pixel_format, gl_pixel_type(dpy), data);
+# endif
+
+ jwxyz_assert_gl ();
+
+ return 0;
+}
+
+/* At the moment only XGetImage and get_xshm_image use XGetSubImage. */
+/* #### Twang calls XGetImage on the window intending to get a
+ buffer full of black. This is returning a buffer full of white
+ instead of black for some reason. */
+static XImage *
+GetSubImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int width, unsigned int height,
+ unsigned long plane_mask, int format,
+ XImage *dest_image, int dest_x, int dest_y)
+{
+ Assert ((width < 65535), "improbably large width");
+ Assert ((height < 65535), "improbably large height");
+ Assert ((x < 65535 && x > -65535), "improbably large x");
+ Assert ((y < 65535 && y > -65535), "improbably large y");
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, d);
+
+ // TODO: What if this reads off the edge? What is supposed to happen?
+
+ {
+ // In case the caller tries to write off the edge.
+ int
+ max_width = dest_image->width - dest_x,
+ max_height = dest_image->height - dest_y;
+
+ if (width > max_width) {
+ width = max_width;
+ }
+
+ if (height > max_height) {
+ height = max_height;
+ }
+ }
+
+ Assert (jwxyz_drawable_depth (d) == dest_image->depth, "XGetSubImage: depth mismatch");
+
+ if (dest_image->depth == visual_depth (NULL, NULL)) {
+ Assert (!(dest_image->bytes_per_line % 4), "XGetSubImage: bytes_per_line not divisible by 4");
+ unsigned pixels_per_line = dest_image->bytes_per_line / 4;
+#ifdef HAVE_JWZGLES
+ Assert (pixels_per_line == width, "XGetSubImage: (TODO) pixels_per_line != width");
+#else
+ glPixelStorei (GL_PACK_ROW_LENGTH, pixels_per_line);
+#endif
+ glPixelStorei (GL_PACK_ALIGNMENT, 4);
+
+ uint32_t *dest_data = (uint32_t *)dest_image->data + pixels_per_line * dest_y + dest_x;
+
+ glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
+ dpy->pixel_format, gl_pixel_type(dpy), dest_data);
+
+ /* Flip this upside down. :( */
+ uint32_t *top = dest_data;
+ uint32_t *bottom = dest_data + pixels_per_line * (height - 1);
+ for (unsigned y = height / 2; y; --y) {
+ for (unsigned x = 0; x != width; ++x) {
+ uint32_t px = top[x];
+ top[x] = bottom[x];
+ bottom[x] = px;
+ }
+ top += pixels_per_line;
+ bottom -= pixels_per_line;
+ }
+ } else {
+
+ uint32_t *rgba_image = malloc(width * height * 4);
+ Assert (rgba_image, "not enough memory");
+
+ // Must be GL_RGBA; GL_RED isn't available.
+ glReadPixels (x, jwxyz_frame (d)->height - (y + height), width, height,
+ GL_RGBA, GL_UNSIGNED_BYTE, rgba_image);
+
+ Assert (!(dest_x % 8), "XGetSubImage: dest_x not byte-aligned");
+ uint8_t *top =
+ (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y
+ + dest_x / 8;
+#if 0
+ {
+ char s[10240];
+ Log("#GI ---------- %d %d %d x %d %08lx",
+ jwxyz_drawable_depth(d), dest_image->depth,
+ width, height,
+ (unsigned long) d);
+ for (int y = 0; y < height; y++) {
+ int x;
+ for (x = 0; x < width; x++) {
+ unsigned long b = rgba_image[(height-y-1) * width + x];
+ s[x] = ( (b & 0xFF000000)
+ ? ((b & 0x00FFFFFF) ? '#' : '-')
+ : ((b & 0x00FFFFFF) ? '+' : ' '));
+ }
+ s[x] = 0;
+ Log("#GI [%s]",s);
+ }
+ Log("#GI ----------");
+ }
+#endif
+ const uint32_t *bottom = rgba_image + width * (height - 1);
+ for (unsigned y = height; y; --y) {
+ memset (top, 0, width / 8);
+ for (unsigned x = 0; x != width; ++x) {
+ if (bottom[x] & 0x80)
+ top[x >> 3] |= (1 << (x & 7));
+ }
+ top += dest_image->bytes_per_line;
+ bottom -= width;
+ }
+
+ free (rgba_image);
+ }
+
+ return dest_image;
+}
+
+
+static int
+SetClipMask (Display *dpy, GC gc, Pixmap m)
+{
+ Assert (m != dpy->main_window, "not a pixmap");
+
+ free_clip_mask (dpy, gc);
+
+ if (!m) {
+ gc->clip_mask = 0;
+ return 0;
+ }
+
+ Assert (jwxyz_drawable_depth (m) == 1, "wrong depth for clip mask");
+
+ const XRectangle *frame = jwxyz_frame (m);
+ gc->clip_mask_width = frame->width;
+ gc->clip_mask_height = frame->height;
+
+ /* Apparently GL_ALPHA isn't a valid format for a texture used in an FBO:
+
+ (86) Are any one- or two- component formats color-renderable?
+
+ Presently none of the one- or two- component texture formats
+ defined in unextended OpenGL is color-renderable. The R
+ and RG float formats defined by the NV_float_buffer
+ extension are color-renderable.
+
+ Although an early draft of the FBO specification permitted
+ rendering into alpha, luminance, and intensity formats, this
+ this capability was pulled when it was realized that it was
+ under-specified exactly how rendering into these formats
+ would work. (specifically, how R/G/B/A map to I/L/A)
+
+ <https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_framebuffer_object.txt>
+
+ ...Therefore, 1-bit drawables get to have wasted color channels.
+ Currently, R=G=B=grey, and the alpha channel is unused.
+ */
+
+ /* There doesn't seem to be any way to move what's on one of the color
+ channels to the alpha channel in GLES 1.1 without leaving the GPU.
+ */
+
+ /* GLES 1.1 only supports GL_CLAMP_TO_EDGE or GL_REPEAT for
+ GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T, so to prevent drawing outside
+ the clip mask pixmap, there's two options:
+ 1. Use glScissor.
+ 2. Add a black border.
+
+ Option #2 is in use here.
+
+ The following converts in-place an RGBA image to alpha-only image with a
+ 1px black border.
+ */
+
+ // Prefix/suffix: 1 row+1 pixel, rounded to nearest int32.
+ size_t pad0 = frame->width + 3, pad = (pad0 + 3) & ~3;
+ uint8_t *data = malloc(2 * pad + frame->width * frame->height * 4);
+
+ uint8_t *rgba = data + pad;
+ uint8_t *alpha = rgba;
+ uint8_t *alpha0 = alpha - pad0;
+ memset (alpha0, 0, pad0); // Top row.
+
+ jwxyz_gl_flush (dpy);
+ jwxyz_bind_drawable (dpy, dpy->main_window, m);
+ glReadPixels (0, 0, frame->width, frame->height, GL_RGBA, GL_UNSIGNED_BYTE,
+ rgba);
+
+ for (unsigned y = 0; y != frame->height; ++y) {
+ for (unsigned x = 0; x != frame->width; ++x)
+ alpha[x] = rgba[x * 4];
+
+ rgba += frame->width * 4;
+
+ alpha += frame->width;
+ alpha[0] = 0;
+ alpha[1] = 0;
+ alpha += 2;
+ }
+
+ alpha -= 2;
+ memset (alpha, 0, pad0); // Bottom row.
+
+ glGenTextures (1, &gc->clip_mask);
+ tex_parameters (dpy, gc->clip_mask);
+
+ glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
+
+ unsigned tex_w = frame->width + 2, tex_h = frame->height + 2;
+ tex_image (dpy, GL_ALPHA, &tex_w, &tex_h, GL_ALPHA, GL_UNSIGNED_BYTE,
+ alpha0);
+
+ free(data);
+
+ return 0;
+}
+
+static int
+SetClipOrigin (Display *dpy, GC gc, int x, int y)
+{
+ gc->gcv.clip_x_origin = x;
+ gc->gcv.clip_y_origin = y;
+ return 0;
+}
+
+void set_points_list(XPoint *points, int npoints, linked_point *root)
+{
+ linked_point *current;
+
+ current = root;
+ for (int i = 0; i < npoints - 2 ; i++) {
+ current->x = points[i].x;
+ current->y = points[i].y;
+ current->next = (linked_point *) malloc(sizeof(linked_point));
+ current = current->next;
+ }
+ current->x = points[npoints-2].x;
+ current->y = points[npoints-2].y;
+ current->next = root;
+}
+
+
+double compute_edge_length(linked_point * a, linked_point * b)
+{
+
+ int xdiff, ydiff, xsq, ysq, added;
+ double xy_add, edge_length;
+
+ xdiff = a->x - b->x;
+ ydiff = a->y - b->y;
+ xsq = xdiff * xdiff;
+ ysq = ydiff * ydiff;
+ added = xsq + ysq;
+ xy_add = (double) added;
+ edge_length = sqrt(xy_add);
+ return edge_length;
+}
+
+double get_angle(double a, double b, double c)
+{
+ double cos_a, i_cos_a;
+ cos_a = (((b * b) + (c * c)) - (a * a)) / (double) (2.0 * b * c);
+ i_cos_a = acos(cos_a);
+ return i_cos_a;
+}
+
+
+Bool is_same_slope(linked_point * a)
+{
+
+ int abx, bcx, aby, bcy, aa, bb;
+ linked_point *b;
+ linked_point *c;
+
+ b = a->next;
+ c = b->next;
+
+ // test if slopes are indefinite for both line segments
+ if (a->x == b->x) {
+ return b->x == c->x;
+ } else if (b->x == c->x) {
+ return False; // false, as ax/bx is not indefinite
+ }
+
+ abx = a->x - b->x;
+ bcx = b->x - c->x;
+ aby = a->y - b->y;
+ bcy = b->y - c->y;
+ aa = abx * bcy;
+ bb = bcx * aby;
+
+ return aa == bb;
+}
+
+void draw_three_vertices(Display *dpy, linked_point * a, Bool triangle)
+{
+
+ linked_point *b;
+ linked_point *c;
+ GLenum drawType;
+
+ b = a->next;
+ c = b->next;
+
+ GLfloat vertices[3][2] = {
+ {a->x, a->y},
+ {b->x, b->y},
+ {c->x, c->y}
+ };
+
+ if (triangle) {
+ drawType = GL_TRIANGLES;
+ } else {
+ drawType = GL_LINES;
+ }
+
+ glEnableClientState(GL_VERTEX_ARRAY);
+ vertex_pointer(dpy, GL_FLOAT, 0, vertices);
+ glDrawArrays(drawType, 0, 3);
+
+ free(b); // cut midpoint off from remaining polygon vertex list
+ a->next = c;
+}
+
+
+Bool is_an_ear(linked_point * a)
+{
+ double edge_ab, edge_bc, edge_ac;
+ double angle_a, angle_b, angle_c;
+ double my_pi;
+ linked_point *b, *c;
+
+ b = a->next;
+ c = b->next;
+ my_pi = (double) M_PI;
+
+ edge_ab = compute_edge_length(a, b);
+ edge_bc = compute_edge_length(b, c);
+ edge_ac = compute_edge_length(a, c);
+ angle_a = get_angle(edge_bc, edge_ab, edge_ac);
+ angle_b = get_angle(edge_ac, edge_ab, edge_bc);
+ angle_c = get_angle(edge_ab, edge_ac, edge_bc);
+
+ return angle_a < my_pi && angle_b < my_pi && angle_c < my_pi;
+}
+
+
+Bool is_three_point_loop(linked_point * head)
+{
+ return head->x == head->next->next->next->x
+ && head->y == head->next->next->next->y;
+}
+
+
+void traverse_points_list(Display *dpy, linked_point * root)
+{
+ linked_point *head;
+ head = root;
+
+ while (!is_three_point_loop(head)) {
+ if (is_an_ear(head)) {
+ draw_three_vertices(dpy, head, True);
+ } else if (is_same_slope(head)) {
+ draw_three_vertices(dpy, head, False);
+ } else {
+ head = head->next;
+ }
+ }
+
+ // handle final three vertices in polygon
+ if (is_an_ear(head)) {
+ draw_three_vertices(dpy, head, True);
+ } else if (is_same_slope(head)) {
+ draw_three_vertices(dpy, head, False);
+ } else {
+ free(head->next->next);
+ free(head->next);
+ free(head);
+ Assert (False, "traverse_points_list: unknown configuration");
+ }
+
+ free(head->next);
+ free(head);
+}
+
+
+const struct jwxyz_vtbl gl_vtbl = {
+ root,
+ visual,
+ display_sources_data,
+
+ window_background,
+ draw_arc,
+ fill_rects,
+ gc_gcv,
+ gc_depth,
+ jwxyz_draw_string,
+
+ jwxyz_gl_copy_area,
+
+ DrawPoints,
+ DrawSegments,
+ CreateGC,
+ FreeGC,
+ ClearWindow,
+ SetClipMask,
+ SetClipOrigin,
+ FillPolygon,
+ DrawLines,
+ PutImage,
+ GetSubImage
+};
+
+#endif /* JWXYZ_GL -- entire file */