+ /* 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
+
+ // The OpenGL extensions in use in here are pretty are pretty much ubiquitous
+ // on OS X, but it's still good form to check.
+ const GLubyte *extensions = glGetString (GL_EXTENSIONS);
+
+ glGenTextures (1, &backbuffer_texture);
+
+ // On really old systems, it would make sense to split the texture
+ // into subsections
+# ifndef USE_IPHONE
+ gl_texture_target = (gluCheckExtension ((const GLubyte *)
+ "GL_ARB_texture_rectangle",
+ extensions)
+ ? GL_TEXTURE_RECTANGLE_EXT : GL_TEXTURE_2D);
+# else
+ // OES_texture_npot also provides this, but iOS never provides it.
+ gl_limited_npot_p = gluCheckExtension ((const GLubyte *)
+ "GL_APPLE_texture_2D_limited_npot",
+ extensions);
+ gl_texture_target = GL_TEXTURE_2D;
+# endif
+
+ glBindTexture (gl_texture_target, &backbuffer_texture);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ // GL_LINEAR might make sense on Retina iPads.
+ glTexParameteri (gl_texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (gl_texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+# ifndef USE_IPHONE
+ // There isn't much sense in supporting one of these if the other
+ // isn't present.
+ gl_apple_client_storage_p =
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_client_storage",
+ extensions) &&
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_range", extensions);
+
+ if (gl_apple_client_storage_p) {
+ glTexParameteri (gl_texture_target, GL_TEXTURE_STORAGE_HINT_APPLE,
+ GL_STORAGE_SHARED_APPLE);
+ glPixelStorei (GL_UNPACK_CLIENT_STORAGE_APPLE, GL_TRUE);
+ }
+# endif
+
+ // 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 USE_IPHONE
+ gl_pixel_format =
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_texture_format_BGRA8888",
+ extensions) ? GL_BGRA : GL_RGBA;
+ gl_pixel_type = GL_UNSIGNED_BYTE;
+ // See also OES_read_format.
+# else
+ if (gl_check_ver (&version, 1, 2) ||
+ (gluCheckExtension ((const GLubyte *)"GL_EXT_bgra", extensions) &&
+ gluCheckExtension ((const GLubyte *)"GL_APPLE_packed_pixels",
+ extensions))) {
+ gl_pixel_format = GL_BGRA;
+ // Both Intel and PowerPC-era docs say to use GL_UNSIGNED_INT_8_8_8_8_REV.
+ gl_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ } else {
+ gl_pixel_format = GL_RGBA;
+ gl_pixel_type = GL_UNSIGNED_BYTE;
+ }
+ // GL_ABGR_EXT/GL_UNSIGNED_BYTE is another possibilty that may have made more
+ // sense on PowerPC.
+# endif
+
+ glEnable (gl_texture_target);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+
+# ifdef USE_IPHONE
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ NSAssert (new_backbuffer_size.width != 0 && new_backbuffer_size.height != 0,
+ @"initial_bounds never got set");
+ // This is pretty similar to the glOrtho in createBackbuffer for OS X.
+ glOrthof (-new_backbuffer_size.width, new_backbuffer_size.width,
+ -new_backbuffer_size.height, new_backbuffer_size.height, -1, 1);
+# endif // USE_IPHONE
+
+ check_gl_error ("enableBackbuffer");
+}
+
+
+static GLsizei
+to_pow2 (size_t x)
+{
+ if (x <= 1)
+ return 1;
+
+ size_t mask = (size_t)-1;
+ unsigned bits = sizeof(x) * CHAR_BIT;
+ unsigned log2 = bits;
+
+ --x;
+ while (bits) {
+ if (!(x & mask)) {
+ log2 -= bits;
+ x <<= bits;
+ }
+
+ bits >>= 1;
+ mask <<= bits;
+ }
+
+ return 1 << log2;
+}
+
+
+/* Create a bitmap context into which we render everything.
+ If the desired size has changed, re-created it.
+ new_size is in rotated pixels, not points: the same size
+ and shape as the X11 window as seen by the hacks.
+ */
+- (void) createBackbuffer:(CGSize)new_size
+{
+ // Colorspaces and CGContexts only happen with non-GL hacks.
+ if (colorspace)
+ CGColorSpaceRelease (colorspace);
+
+# ifdef BACKBUFFER_OPENGL
+ NSAssert ([NSOpenGLContext currentContext] ==
+ ogl_ctx, @"invalid GL context");
+
+ // This almost isn't necessary, except for the ugly aliasing artifacts.
+# ifndef USE_IPHONE
+ glViewport (0, 0, new_size.width, new_size.height);
+
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+ // This is pretty similar to the glOrthof in enableBackbuffer for iPhone.
+ glOrtho (-new_size.width, new_size.width, -new_size.height, new_size.height,
+ -1, 1);
+# endif // !USE_IPHONE
+# endif // BACKBUFFER_OPENGL
+
+ NSWindow *window = [self window];
+
+ if (window && xdpy) {
+ [self lockFocus];
+
+# ifdef BACKBUFFER_OPENGL
+ // Was apparently faster until 10.9.
+ colorspace = CGColorSpaceCreateDeviceRGB ();
+# endif // BACKBUFFER_OPENGL
+
+ [self unlockFocus];
+ } else {
+ colorspace = CGColorSpaceCreateDeviceRGB();
+ }
+
+ if (backbuffer &&
+ (int)backbuffer_size.width == (int)new_size.width &&
+ (int)backbuffer_size.height == (int)new_size.height)
+ return;
+
+ CGContextRef ob = backbuffer;
+ void *odata = backbuffer_data;
+ size_t olen = backbuffer_len;
+
+ CGSize osize = backbuffer_size; // pixels, not points.
+ backbuffer_size = new_size; // pixels, not points.
+
+# if TARGET_IPHONE_SIMULATOR
+ NSLog(@"backbuffer %.0fx%.0f",
+ backbuffer_size.width, backbuffer_size.height);
+# endif
+
+ /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in
+ <https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html>.
+
+ iOS uses bog-standard glTexImage2D (for now).
+
+ glMapBuffer is the standard way to get data from system RAM to video
+ memory asynchronously and without a memcpy, but support for
+ APPLE_client_storage is ubiquitous on OS X (not so for glMapBuffer),
+ and on iOS GL_PIXEL_UNPACK_BUFFER is only available on OpenGL ES 3
+ (iPhone 5S or newer). Plus, glMapBuffer doesn't work well with
+ CGBitmapContext: glMapBuffer can return a different pointer on each
+ call, but a CGBitmapContext doesn't allow its data pointer to be
+ changed -- and recreating the context for a new pointer can be
+ expensive (glyph caches get dumped, for instance).
+
+ glMapBufferRange has MAP_FLUSH_EXPLICIT_BIT and MAP_UNSYNCHRONIZED_BIT,
+ and these seem to allow mapping the buffer and leaving it where it is
+ in client address space while OpenGL works with the buffer, but it
+ requires OpenGL 3 Core profile on OS X (and ES 3 on iOS for
+ GL_PIXEL_UNPACK_BUFFER), so point goes to APPLE_client_storage.
+
+ AMD_pinned_buffer provides the same advantage as glMapBufferRange, but
+ Apple never implemented that one for OS X.
+ */
+
+ backbuffer_data = NULL;
+ gl_texture_w = (int)backbuffer_size.width;
+ gl_texture_h = (int)backbuffer_size.height;
+
+ NSAssert (gl_texture_target == GL_TEXTURE_2D
+# ifndef USE_IPHONE
+ || gl_texture_target == GL_TEXTURE_RECTANGLE_EXT
+# endif
+ , @"unexpected GL texture target");
+
+# ifndef USE_IPHONE
+ if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
+# else
+ if (!gl_limited_npot_p)
+# endif
+ {
+ gl_texture_w = to_pow2 (gl_texture_w);
+ gl_texture_h = to_pow2 (gl_texture_h);
+ }
+
+ size_t bytes_per_row = gl_texture_w * 4;
+
+# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ // APPLE_client_storage requires texture width to be aligned to 32 bytes, or
+ // it will fall back to a memcpy.
+ // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html#//apple_ref/doc/uid/TP40001987-CH407-SW24
+ bytes_per_row = (bytes_per_row + 31) & ~31;
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+ backbuffer_len = bytes_per_row * gl_texture_h;
+ if (backbuffer_len) // mmap requires this to be non-zero.
+ backbuffer_data = mmap (NULL, backbuffer_len,
+ PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED,
+ -1, 0);
+
+ BOOL alpha_first_p, order_little_p;
+
+ if (gl_pixel_format == GL_BGRA) {
+ alpha_first_p = YES;
+ order_little_p = YES;
+/*
+ } else if (gl_pixel_format == GL_ABGR_EXT) {
+ alpha_first_p = NO;
+ order_little_p = YES; */
+ } else {
+ NSAssert (gl_pixel_format == GL_RGBA, @"unknown GL pixel format");
+ alpha_first_p = NO;
+ order_little_p = NO;
+ }
+
+#ifdef USE_IPHONE
+ NSAssert (gl_pixel_type == GL_UNSIGNED_BYTE, @"unknown GL pixel type");
+#else
+ NSAssert (gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8 ||
+ gl_pixel_type == GL_UNSIGNED_INT_8_8_8_8_REV ||
+ gl_pixel_type == GL_UNSIGNED_BYTE,
+ @"unknown GL pixel type");
+
+#if defined __LITTLE_ENDIAN__
+ const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8;
+#elif defined __BIG_ENDIAN__
+ const GLenum backwards_pixel_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+#else
+# error Unknown byte order.
+#endif
+
+ if (gl_pixel_type == backwards_pixel_type)
+ order_little_p ^= YES;
+#endif
+
+ CGBitmapInfo bitmap_info =
+ (alpha_first_p ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaNoneSkipLast) |
+ (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big);
+
+ backbuffer = CGBitmapContextCreate (backbuffer_data,
+ (int)backbuffer_size.width,
+ (int)backbuffer_size.height,
+ 8,
+ bytes_per_row,
+ colorspace,
+ bitmap_info);
+ NSAssert (backbuffer, @"unable to allocate back buffer");
+
+ // Clear it.
+ CGRect r;
+ r.origin.x = r.origin.y = 0;
+ r.size = backbuffer_size;
+ CGContextSetGrayFillColor (backbuffer, 0, 1);
+ CGContextFillRect (backbuffer, r);
+
+# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ if (gl_apple_client_storage_p)
+ glTextureRangeAPPLE (gl_texture_target, backbuffer_len, backbuffer_data);
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+ if (ob) {
+ // Restore old bits, as much as possible, to the X11 upper left origin.
+
+ CGRect rect; // pixels, not points
+ rect.origin.x = 0;
+ rect.origin.y = (backbuffer_size.height - osize.height);
+ rect.size = osize;
+
+ CGImageRef img = CGBitmapContextCreateImage (ob);
+ CGContextDrawImage (backbuffer, rect, img);
+ CGImageRelease (img);
+ CGContextRelease (ob);
+
+ if (olen)
+ // munmap should round len up to the nearest page.
+ munmap (odata, olen);
+ }
+
+ check_gl_error ("createBackbuffer");
+}
+
+
+- (void) drawBackbuffer
+{
+# ifdef BACKBUFFER_OPENGL
+
+ NSAssert ([ogl_ctx isKindOfClass:[NSOpenGLContext class]],
+ @"ogl_ctx is not an NSOpenGLContext");
+
+ NSAssert (! (CGBitmapContextGetBytesPerRow (backbuffer) % 4),
+ @"improperly-aligned backbuffer");
+
+ // This gets width and height from the backbuffer in case
+ // APPLE_client_storage is in use. See the note in createBackbuffer.
+ // This still has to happen every frame even when APPLE_client_storage has
+ // the video adapter pulling texture data straight from
+ // XScreenSaverView-owned memory.
+ glTexImage2D (gl_texture_target, 0, GL_RGBA,
+ (GLsizei)(CGBitmapContextGetBytesPerRow (backbuffer) / 4),
+ gl_texture_h, 0, gl_pixel_format, gl_pixel_type,
+ backbuffer_data);
+
+ GLfloat vertices[4][2] =
+ {
+ {-backbuffer_size.width, backbuffer_size.height},
+ { backbuffer_size.width, backbuffer_size.height},
+ { backbuffer_size.width, -backbuffer_size.height},
+ {-backbuffer_size.width, -backbuffer_size.height}
+ };
+
+ GLfloat tex_coords[4][2];
+
+# ifndef USE_IPHONE
+ if (gl_texture_target == GL_TEXTURE_RECTANGLE_EXT) {
+ tex_coords[0][0] = 0;
+ tex_coords[0][1] = 0;
+ tex_coords[1][0] = backbuffer_size.width;
+ tex_coords[1][1] = 0;
+ tex_coords[2][0] = backbuffer_size.width;
+ tex_coords[2][1] = backbuffer_size.height;
+ tex_coords[3][0] = 0;
+ tex_coords[3][1] = backbuffer_size.height;
+ } else
+# endif // USE_IPHONE
+ {
+ GLfloat x = backbuffer_size.width / gl_texture_w;
+ GLfloat y = backbuffer_size.height / gl_texture_h;
+ tex_coords[0][0] = 0;
+ tex_coords[0][1] = 0;
+ tex_coords[1][0] = x;
+ tex_coords[1][1] = 0;
+ tex_coords[2][0] = x;
+ tex_coords[2][1] = y;
+ tex_coords[3][0] = 0;
+ tex_coords[3][1] = y;
+ }
+
+# ifdef USE_IPHONE
+ if (!ignore_rotation_p) {
+ glMatrixMode (GL_MODELVIEW);
+ glLoadIdentity();
+ glRotatef (rot_current_angle, 0, 0, -1);
+
+ if (rotation_ratio >= 0)
+ glClear (GL_COLOR_BUFFER_BIT);
+ }
+# endif // USE_IPHONE
+
+ glVertexPointer (2, GL_FLOAT, 0, vertices);
+ glTexCoordPointer (2, GL_FLOAT, 0, tex_coords);
+ glDrawArrays (GL_TRIANGLE_FAN, 0, 4);
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ check_gl_error ("drawBackbuffer");
+# endif
+
+ // This can also happen near the beginning of render_x11.
+ [self flushBackbuffer];
+
+# endif // BACKBUFFER_OPENGL
+}
+
+
+- (void)flushBackbuffer
+{
+# ifndef USE_IPHONE
+ // The OpenGL pipeline is not automatically synchronized with the contents
+ // of the backbuffer, so without glFinish, OpenGL can start rendering from
+ // the backbuffer texture at the same time that JWXYZ is clearing and
+ // drawing the next frame in the backing store for the backbuffer texture.
+ glFinish();
+
+ if (double_buffered_p)
+ [ogl_ctx flushBuffer]; // despite name, this actually swaps
+# else
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+ [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
+# endif
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ // glGetError waits for the OpenGL command pipe to flush, so skip it in
+ // release builds.
+ // OpenGL Programming Guide for Mac -> OpenGL Application Design
+ // Strategies -> Allow OpenGL to Manage Your Resources
+ // https://developer.apple.com/library/mac/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_designstrategies/opengl_designstrategies.html#//apple_ref/doc/uid/TP40001987-CH2-SW7
+ check_gl_error ("flushBackbuffer");
+# endif
+}
+
+
+#endif // USE_BACKBUFFER
+
+
+/* Inform X11 that the size of our window has changed.
+ */
+- (void) resize_x11
+{
+ if (!xwindow) return; // early
+
+ CGSize new_size; // pixels, not points
+
+# ifdef USE_BACKBUFFER
+
+ [self prepareContext];
+
+# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ [ogl_ctx update];
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+# ifdef USE_IPHONE
+ CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow])
+ ? initial_bounds
+ : rot_current_size);
+ new_size.width = rotsize.width;
+ new_size.height = rotsize.height;
+# else // !USE_IPHONE
+ new_size = NSSizeToCGSize([self bounds].size);
+# endif // !USE_IPHONE
+
+ [self createBackbuffer:new_size];
+ jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
+ backbuffer);
+# else // !USE_BACKBUFFER
+ new_size = [self bounds].size;
+ jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height,
+ 0);
+# endif // !USE_BACKBUFFER
+
+# if TARGET_IPHONE_SIMULATOR
+ NSLog(@"reshape %.0fx%.0f", new_size.width, new_size.height);
+# endif
+
+ // Next time render_x11 is called, run the saver's reshape_cb.
+ resized_p = YES;
+}
+
+
+- (void) render_x11
+{
+# ifdef USE_IPHONE
+ @try {
+
+ if (orientation == UIDeviceOrientationUnknown)
+ [self didRotate:nil];
+ [self hackRotation];
+# endif
+
+ if (!initted_p) {
+
+ if (! xdpy) {
+# ifdef USE_BACKBUFFER
+ NSAssert (backbuffer, @"no back buffer");
+ xdpy = jwxyz_make_display (self, backbuffer);
+# else
+ xdpy = jwxyz_make_display (self, 0);
+# endif
+ xwindow = XRootWindow (xdpy, 0);
+
+# ifdef USE_IPHONE
+ /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
+ ignore_rotation_p =
+ get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
+# endif // USE_IPHONE
+
+ [self resize_x11];
+ }
+
+ if (!setup_p) {
+ setup_p = YES;
+ if (xsft->setup_cb)
+ xsft->setup_cb (xsft, xsft->setup_arg);
+ }
+ initted_p = YES;
+ resized_p = NO;
+ NSAssert(!xdata, @"xdata already initialized");
+
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ XSetWindowBackground (xdpy, xwindow,
+ get_pixel_resource (xdpy, 0,
+ "background", "Background"));
+ XClearWindow (xdpy, xwindow);
+
+# ifndef USE_IPHONE
+ [[self window] setAcceptsMouseMovedEvents:YES];
+# endif
+
+ /* In MacOS 10.5, this enables "QuartzGL", meaning that the Quartz
+ drawing primitives will run on the GPU instead of the CPU.
+ It seems like it might make things worse rather than better,
+ though... Plus it makes us binary-incompatible with 10.4.
+
+# if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
+ [[self window] setPreferredBackingLocation:
+ NSWindowBackingLocationVideoMemory];
+# endif
+ */
+
+ /* Kludge: even though the init_cb functions are declared to take 2 args,
+ actually call them with 3, for the benefit of xlockmore_init() and
+ xlockmore_setup().
+ */
+ void *(*init_cb) (Display *, Window, void *) =
+ (void *(*) (Display *, Window, void *)) xsft->init_cb;
+
+ xdata = init_cb (xdpy, xwindow, xsft->setup_arg);
+ // NSAssert(xdata, @"no xdata from init");
+ if (! xdata) abort();
+
+ if (get_boolean_resource (xdpy, "doFPS", "DoFPS")) {
+ fpst = fps_init (xdpy, xwindow);
+ if (! xsft->fps_cb) xsft->fps_cb = screenhack_do_fps;
+ } else {
+ fpst = NULL;
+ xsft->fps_cb = 0;
+ }
+
+ [self checkForUpdates];
+ }
+
+
+ /* I don't understand why we have to do this *every frame*, but we do,
+ or else the cursor comes back on.
+ */
+# ifndef USE_IPHONE
+ if (![self isPreview])
+ [NSCursor setHiddenUntilMouseMoves:YES];
+# endif
+
+
+ if (fpst)