+ glEnable (gl_texture_target);
+ glEnableClientState (GL_VERTEX_ARRAY);
+ glEnableClientState (GL_TEXTURE_COORD_ARRAY);
+
+ check_gl_error ("enableBackbuffer");
+}
+
+
+#ifdef USE_IPHONE
+- (BOOL) suppressRotationAnimation
+{
+ return [self ignoreRotation]; // Don't animate if we aren't rotating
+}
+
+- (BOOL) rotateTouches
+{
+ return FALSE; // Adjust event coordinates only if rotating
+}
+#endif
+
+
+- (void) setViewport
+{
+# ifdef BACKBUFFER_OPENGL
+ NSAssert ([NSOpenGLContext currentContext] ==
+ ogl_ctx, @"invalid GL context");
+
+ NSSize new_size = self.bounds.size;
+
+# ifdef USE_IPHONE
+ GLfloat s = self.contentScaleFactor;
+# else // !USE_IPHONE
+ const GLfloat s = self.window.backingScaleFactor;
+# endif
+ GLfloat hs = self.hackedContentScaleFactor;
+
+ // On OS X this almost isn't necessary, except for the ugly aliasing
+ // artifacts.
+ glViewport (0, 0, new_size.width * s, new_size.height * s);
+
+ glMatrixMode (GL_PROJECTION);
+ glLoadIdentity();
+# ifdef USE_IPHONE
+ glOrthof
+# else
+ glOrtho
+# endif
+ (-new_size.width * hs, new_size.width * hs,
+ -new_size.height * hs, new_size.height * hs,
+ -1, 1);
+
+# ifdef USE_IPHONE
+ if ([self ignoreRotation]) {
+ int o = (int) -current_device_rotation();
+ glRotatef (o, 0, 0, 1);
+ }
+# endif // USE_IPHONE
+# endif // BACKBUFFER_OPENGL
+}
+
+
+/* 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
+{
+ CGSize osize = CGSizeZero;
+ if (backbuffer) {
+ osize.width = CGBitmapContextGetWidth(backbuffer);
+ osize.height = CGBitmapContextGetHeight(backbuffer);
+ }
+
+ if (backbuffer &&
+ (int)osize.width == (int)new_size.width &&
+ (int)osize.height == (int)new_size.height)
+ return;
+
+ CGContextRef ob = backbuffer;
+ void *odata = backbuffer_data;
+ GLsizei olen = backbuffer_len;
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ NSLog(@"backbuffer %.0fx%.0f",
+ new_size.width, new_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)new_size.width;
+ gl_texture_h = (int)new_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 = (GLsizei) to_pow2 (gl_texture_w);
+ gl_texture_h = (GLsizei) to_pow2 (gl_texture_h);
+ }
+
+ GLsizei 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)new_size.width,
+ (int)new_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 = new_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 = (new_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 w = xwindow->frame.width, h = xwindow->frame.height;
+
+ GLfloat vertices[4][2] = {{-w, h}, {w, h}, {w, -h}, {-w, -h}};
+
+ GLfloat tex_coords[4][2];
+
+# ifndef USE_IPHONE
+ if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT)
+# endif // USE_IPHONE
+ {
+ w /= gl_texture_w;
+ h /= gl_texture_h;
+ }
+
+ tex_coords[0][0] = 0;
+ tex_coords[0][1] = 0;
+ tex_coords[1][0] = w;
+ tex_coords[1][1] = 0;
+ tex_coords[2][0] = w;
+ tex_coords[2][1] = h;
+ tex_coords[3][0] = 0;
+ tex_coords[3][1] = h;
+
+ 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
+# endif // BACKBUFFER_OPENGL
+}
+
+#endif // JWXYZ_QUARTZ
+
+#ifdef JWXYZ_GL
+
+- (void)enableBackbuffer:(CGSize)new_backbuffer_size;
+{
+ jwxyz_set_matrices (new_backbuffer_size.width, new_backbuffer_size.height);
+ check_gl_error ("enableBackbuffer");
+}
+
+- (void)createBackbuffer:(CGSize)new_size
+{
+ NSAssert ([NSOpenGLContext currentContext] ==
+ ogl_ctx, @"invalid GL context");
+ NSAssert (xwindow->window.current_drawable == xwindow,
+ @"current_drawable not set properly");
+
+# ifndef USE_IPHONE
+ /* On iOS, Retina means glViewport gets called with the screen size instead
+ of the backbuffer/xwindow size. This happens in startAnimation.
+
+ The GL screenhacks call glViewport themselves.
+ */
+ glViewport (0, 0, new_size.width, new_size.height);
+# endif
+
+ // TODO: Preserve contents on resize.
+ glClear (GL_COLOR_BUFFER_BIT);
+ check_gl_error ("createBackbuffer");
+}
+
+#endif // JWXYZ_GL
+
+
+- (void)flushBackbuffer
+{
+# ifdef JWXYZ_GL
+ // Make sure the right context is active: there's two under JWXYZ_GL.
+ jwxyz_bind_drawable (xwindow, xwindow);
+# endif // JWXYZ_GL
+
+# ifndef USE_IPHONE
+
+# ifdef JWXYZ_QUARTZ
+ // 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.
+ // This is only a concern under JWXYZ_QUARTZ because of
+ // APPLE_client_storage; JWXYZ_GL doesn't use that.
+ glFinish();
+# endif // JWXYZ_QUARTZ
+
+ // If JWXYZ_GL was single-buffered, there would need to be a glFinish (or
+ // maybe just glFlush?) here, because single-buffered contexts don't always
+ // update what's on the screen after drawing finishes. (i.e., in safe mode)
+
+# ifdef JWXYZ_QUARTZ
+ // JWXYZ_GL is always double-buffered.
+ if (double_buffered_p)
+# endif // JWXYZ_QUARTZ
+ [ogl_ctx flushBuffer]; // despite name, this actually swaps
+# else // USE_IPHONE
+
+ // jwxyz_bind_drawable() only binds the framebuffer, not the renderbuffer.
+# ifdef JWXYZ_GL
+ GLint gl_renderbuffer = xwindow->gl_renderbuffer;
+# endif
+
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+ [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES];
+# endif // USE_IPHONE
+
+# 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
+}
+
+
+/* Inform X11 that the size of our window has changed.
+ */
+- (void) resize_x11
+{
+ if (!xdpy) return; // early
+
+ NSSize new_size; // pixels, not points
+
+ new_size = self.bounds.size;
+
+# ifdef USE_IPHONE
+
+ // If this hack ignores rotation, then that means that it pretends to
+ // always be in portrait mode. If the View has been resized to a
+ // landscape shape, swap width and height to keep the backbuffer
+ // in portrait.
+ //
+ double rot = current_device_rotation();
+ if ([self ignoreRotation] && (rot == 90 || rot == -90)) {
+ CGFloat swap = new_size.width;
+ new_size.width = new_size.height;
+ new_size.height = swap;
+ }
+# endif // USE_IPHONE
+
+ double s = self.hackedContentScaleFactor;
+ new_size.width *= s;
+ new_size.height *= s;
+
+ [self prepareContext];
+ [self setViewport];
+
+ // On first resize, xwindow->frame is 0x0.
+ if (xwindow->frame.width == new_size.width &&
+ xwindow->frame.height == new_size.height)
+ return;
+
+# if defined(BACKBUFFER_OPENGL) && !defined(USE_IPHONE)
+ [ogl_ctx update];
+# endif // BACKBUFFER_OPENGL && !USE_IPHONE
+
+ NSAssert (xwindow && xwindow->type == WINDOW, @"not a window");
+ xwindow->frame.x = 0;
+ xwindow->frame.y = 0;
+ xwindow->frame.width = new_size.width;
+ xwindow->frame.height = new_size.height;
+
+ [self createBackbuffer:CGSizeMake(xwindow->frame.width,
+ xwindow->frame.height)];
+
+# if defined JWXYZ_QUARTZ
+ xwindow->cgc = backbuffer;
+ NSAssert (xwindow->cgc, @"no CGContext");
+# elif defined JWXYZ_GL && !defined USE_IPHONE
+ [ogl_ctx update];
+ [ogl_ctx setView:xwindow->window.view]; // (Is this necessary?)
+# endif // JWXYZ_GL && USE_IPHONE
+
+ jwxyz_window_resized (xdpy);
+
+# if !defined __OPTIMIZE__ || 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;
+}
+
+
+#ifdef USE_IPHONE
+
+/* Called by SaverRunner when the device has changed orientation.
+ That means we need to generate a resize event, even if the size
+ has not changed (e.g., from LandscapeLeft to LandscapeRight).
+ */
+- (void) orientationChanged
+{
+ [self setViewport];
+ resized_p = YES;
+ next_frame_time = 0; // Get a new frame on screen quickly
+}
+
+/* A hook run after the 'reshape_' method has been called. Used by
+ XScreenSaverGLView to adjust the in-scene GL viewport.
+ */
+- (void) postReshape
+{
+}
+#endif // USE_IPHONE
+
+
+// Only render_x11 should call this. XScreenSaverGLView specializes it.
+- (void) reshape_x11
+{
+ xsft->reshape_cb (xdpy, xwindow, xdata,
+ xwindow->frame.width, xwindow->frame.height);
+}
+
+- (void) render_x11
+{
+# ifdef USE_IPHONE
+ @try {
+# endif
+
+ // jwxyz_make_display needs this.
+ [self prepareContext]; // resize_x11 also calls this.
+
+ if (!initted_p) {
+
+ resized_p = NO;
+
+ if (! xdpy) {
+# ifdef JWXYZ_QUARTZ
+ xwindow->cgc = backbuffer;
+# endif // JWXYZ_QUARTZ
+ xdpy = jwxyz_quartz_make_display (xwindow);
+
+# if defined USE_IPHONE
+ /* Some X11 hacks (fluidballs) want to ignore all rotation events. */
+ _ignoreRotation =
+# ifdef JWXYZ_GL
+ TRUE; // Rotation doesn't work yet. TODO: Make rotation work.
+# else // !JWXYZ_GL
+ get_boolean_resource (xdpy, "ignoreRotation", "IgnoreRotation");
+# endif // !JWXYZ_GL
+# endif // USE_IPHONE
+
+ _lowrez_p = get_boolean_resource (xdpy, "lowrez", "Lowrez");
+ if (_lowrez_p) {
+ resized_p = YES;
+
+# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR
+ NSSize b = [self bounds].size;
+ CGFloat s = self.hackedContentScaleFactor;
+# ifdef USE_IPHONE
+ CGFloat o = self.contentScaleFactor;
+# else
+ CGFloat o = self.window.backingScaleFactor;
+# endif
+ if (o != s)
+ NSLog(@"lowrez: scaling %.0fx%.0f -> %.0fx%.0f (%.02f)",
+ b.width * o, b.height * o,
+ b.width * s, b.height * s, s);
+# endif
+ }
+
+ [self resize_x11];
+ }
+
+ if (!setup_p) {
+ setup_p = YES;
+ if (xsft->setup_cb)
+ xsft->setup_cb (xsft, xsft->setup_arg);
+ }
+ initted_p = YES;
+ NSAssert(!xdata, @"xdata already initialized");
+
+
+# undef ya_rand_init
+ ya_rand_init (0);
+
+ XSetWindowBackground (xdpy, xwindow,
+ get_pixel_resource (xdpy, 0,