X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=jwxyz%2Fjwxyz-gl.c;h=9bf2661e6982592ea1748c3373f2012da5e5271e;hp=41006033b7ebeb2a83da6fedf21e8413bce6d85c;hb=78add6e627ee5f10e1fa6f3852602ea5066eee5a;hpb=39809ded547bdbb08207d3e514950425215b4410 diff --git a/jwxyz/jwxyz-gl.c b/jwxyz/jwxyz-gl.c index 41006033..9bf2661e 100644 --- a/jwxyz/jwxyz-gl.c +++ b/jwxyz/jwxyz-gl.c @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 1991-2017 Jamie Zawinski +/* xscreensaver, Copyright (c) 1991-2018 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -102,11 +102,20 @@ #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. @@ -119,9 +128,7 @@ struct jwxyz_Display { /* Bool opengl_core_p */; GLenum gl_texture_target; -// #if defined USE_IPHONE - GLuint rect_texture; // Also can work on the desktop. -// #endif + GLuint textures[2]; // Also can work on the desktop. unsigned long window_background; @@ -132,7 +139,7 @@ struct jwxyz_Display { size_t queue_size, queue_capacity; Drawable queue_drawable; GLint queue_mode; - GLshort *queue_vertex; + void *queue_vertex; uint32_t *queue_color; Bool queue_line_cap; }; @@ -320,29 +327,30 @@ jwxyz_gl_make_display (Window w) Log ("GL_MAX_TEXTURE_SIZE: %d\n", max_texture_size); } - // In case a GL hack wants to use X11 to draw offscreen, the rect_texture is available. - Assert (d->main_window == w, "Uh-oh."); - glGenTextures (1, &d->rect_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, d->rect_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. + glGenTextures (countof (d->textures), d->textures); + + for (unsigned i = 0; i != countof (d->textures); i++) { + // 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, d->textures[i]); + // 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; + const GLint wrap = GL_CLAMP; # else // HAVE_JWZGLES - const GLint wrap = GL_CLAMP_TO_EDGE; + 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); + // 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); + } d->gc_function = GXcopy; d->gc_alpha_allowed_p = False; @@ -354,6 +362,8 @@ jwxyz_gl_make_display (Window w) 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); @@ -370,6 +380,8 @@ jwxyz_gl_free_display (Display *dpy) 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; @@ -466,8 +478,9 @@ visual (Display *dpy) subwindow_mode */ -static GLshort * -enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count) +static void * +enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count, + unsigned long pixel) { if (dpy->queue_size && (dpy->gc_function != gc->gcv.function || @@ -480,6 +493,15 @@ enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count) 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; @@ -488,8 +510,12 @@ enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count) 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(*dpy->queue_vertex) * 2 * dpy->queue_capacity); + dpy->queue_vertex, sizeof(GLfloat) * 2 * dpy->queue_capacity); if (!new_color || !new_vertex) return NULL; @@ -503,11 +529,29 @@ enqueue (Display *dpy, Drawable d, GC gc, int mode, size_t count) union color_bytes color; // TODO: validate color - JWXYZ_QUERY_COLOR (dpy, gc->gcv.foreground, 0xffull, color.bytes); + 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; - return dpy->queue_vertex + old_size * 2; + 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]; + } } @@ -641,7 +685,8 @@ DrawPoints (Display *dpy, Drawable d, GC gc, short v[2] = {0, 0}; // TODO: XPoints can be fed directly to OpenGL. - GLshort *gl_points = enqueue (dpy, d, gc, GL_POINTS, count); // TODO: enqueue returns NULL. + 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]; @@ -689,6 +734,8 @@ set_white (void) void jwxyz_gl_flush (Display *dpy) { + Assert (dpy->vtbl == &gl_vtbl, "jwxyz-gl.c: bad vtable"); + if (!dpy->queue_size) return; @@ -709,7 +756,9 @@ jwxyz_gl_flush (Display *dpy) } glColorPointer (4, GL_UNSIGNED_BYTE, 0, dpy->queue_color); - glVertexPointer (2, GL_SHORT, 0, dpy->queue_vertex); + glVertexPointer (2, + 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? @@ -717,7 +766,8 @@ jwxyz_gl_flush (Display *dpy) Assert (!(dpy->queue_size % 2), "bad count for GL_LINES"); glColorPointer (4, GL_UNSIGNED_BYTE, sizeof(GLubyte) * 8, dpy->queue_color); - glVertexPointer (2, GL_SHORT, sizeof(GLshort) * 4, dpy->queue_vertex + 2); + glVertexPointer (2, GL_SHORT, sizeof(GLshort) * 4, + (GLshort *)dpy->queue_vertex + 2); glDrawArrays (GL_POINTS, 0, dpy->queue_size / 2); } @@ -752,7 +802,8 @@ jwxyz_gl_copy_area_read_tex_image (Display *dpy, unsigned src_height, GLint internalformat = texture_internalformat(dpy); - glBindTexture (dpy->gl_texture_target, dpy->rect_texture); + /* 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, @@ -779,7 +830,8 @@ jwxyz_gl_copy_area_write_tex_image (Display *dpy, GC gc, int src_x, int src_y, tex_h = to_pow2(tex_h); } - glBindTexture (dpy->gl_texture_target, dpy->rect_texture); + /* 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->gl_texture_target, tex_w, tex_h, 0, 0, width, height, dst_x, dst_y); @@ -904,7 +956,9 @@ DrawLines (Display *dpy, Drawable d, GC gc, XPoint *points, int count, // // TODO: Fix epicycle hack with large thickness, and truchet line segment ends // -static void drawThickLine(int line_width, XSegment *segments) +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; @@ -940,19 +994,17 @@ static void drawThickLine(int line_width, XSegment *segments) float x6 = sx2 + wsn; float y6 = sy2 - csn; - GLfloat coords[4][2] = - { - {x3, y3}, - {x4, y4}, - {x6, y6}, - {x5, y5} - }; - - glEnableClientState (GL_VERTEX_ARRAY); - glVertexPointer (2, GL_FLOAT, 0, coords); - jwxyz_assert_gl (); - glDrawArrays (GL_TRIANGLE_FAN, 0, 4); - jwxyz_assert_gl (); + 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); } @@ -963,8 +1015,7 @@ DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) /* Thin lines <= 1px are offset by +0.5; thick lines are not. */ if (count == 1 && gc->gcv.line_width > 1) { - set_fg_gc (dpy, d, gc); - drawThickLine(gc->gcv.line_width,segments); + drawThickLine (dpy, d, gc, gc->gcv.line_width, segments); } else { if (dpy->queue_line_cap != (gc->gcv.cap_style != CapNotLast)) @@ -978,8 +1029,8 @@ DrawSegments (Display *dpy, Drawable d, GC gc, XSegment *segments, int count) offsetof(XSegment, x2) == 4, "XDrawSegments: Data alignment mix-up."); - memcpy (enqueue(dpy, d, gc, GL_LINES, count * 2), segments, - count * sizeof(XSegment)); + memcpy (enqueue(dpy, d, gc, GL_LINES, count * 2, gc->gcv.foreground), + segments, count * sizeof(XSegment)); } return 0; @@ -1013,40 +1064,18 @@ fill_rects (Display *dpy, Drawable d, GC gc, const XRectangle *rectangles, unsigned long nrectangles, unsigned long pixel) { - set_color_gc (dpy, d, gc, pixel); - -/* - glBegin(GL_QUADS); - for (unsigned i = 0; i != nrectangles; ++i) { + for (unsigned long i = 0; i != nrectangles; ++i) { const XRectangle *r = &rectangles[i]; - glVertex2i(r->x, r->y); - glVertex2i(r->x, r->y + r->height); - glVertex2i(r->x + r->width, r->y + r->height); - glVertex2i(r->x + r->width, r->y); - } - glEnd(); -*/ - - glEnableClientState (GL_VERTEX_ARRAY); - glDisableClientState (GL_TEXTURE_COORD_ARRAY); - - for (unsigned long i = 0; i != nrectangles; ++i) - { - const XRectangle *r = &rectangles[i]; - - GLfloat coords[4][2] = - { - {r->x, r->y}, - {r->x, r->y + r->height}, - {r->x + r->width, r->y + r->height}, - {r->x + r->width, r->y} - }; - - // TODO: Several rects at once. Maybe even tune for XScreenSaver workloads. - glVertexPointer (2, GL_FLOAT, 0, coords); - jwxyz_assert_gl (); - glDrawArrays (GL_TRIANGLE_FAN, 0, 4); - jwxyz_assert_gl (); + 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); } } @@ -1394,6 +1423,7 @@ PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, 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); @@ -1406,6 +1436,7 @@ PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, 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 @@ -1430,6 +1461,27 @@ PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, 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) { @@ -1448,10 +1500,30 @@ PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, 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); @@ -1469,7 +1541,7 @@ PutImage (Display *dpy, Drawable d, GC gc, XImage *ximage, tex_h = to_pow2(tex_h); } - glBindTexture (dpy->gl_texture_target, dpy->rect_texture); + 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. @@ -1616,16 +1688,50 @@ GetSubImage (Display *dpy, Drawable d, int x, int y, } } else { - /* TODO: Actually get pixels. */ + 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 *dest_data = + uint8_t *top = (uint8_t *)dest_image->data + dest_image->bytes_per_line * dest_y + dest_x / 8; - for (unsigned y = height / 2; y; --y) { - memset (dest_data, y & 1 ? 0x55 : 0xAA, width / 8); - dest_data += dest_image->bytes_per_line; +#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;