diff options
Diffstat (limited to 'jwxyz/jwxyz-gl.c')
-rw-r--r-- | jwxyz/jwxyz-gl.c | 2125 |
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 */ |