+# ifndef USE_IPHONE
+ struct gl_version version;
+
+ {
+ const char *version_str = (const char *)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
+
+ // 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 = jwzgles_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 =
+ jwzgles_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);
+
+ 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;
+ GLfloat hs = self.hackedContentScaleFactor;
+# else // !USE_IPHONE
+ const GLfloat s = 1;
+ const GLfloat hs = s;
+# endif
+
+ // 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;
+ }
+
+ double s = self.hackedContentScaleFactor;
+ new_size.width *= s;
+ new_size.height *= s;
+# endif // USE_IPHONE
+
+ [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) {
+
+ if (! xdpy) {
+# ifdef JWXYZ_QUARTZ
+ xwindow->cgc = backbuffer;
+# endif // JWXYZ_QUARTZ
+ xdpy = jwxyz_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
+
+ [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);
+ fps_cb = xsft->fps_cb;
+ if (! fps_cb) fps_cb = screenhack_do_fps;
+ } else {
+ fpst = NULL;
+ fps_cb = 0;
+ }
+
+# ifdef USE_IPHONE
+ if (current_device_rotation() != 0) // launched while rotated
+ resized_p = YES;
+# endif
+
+ [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)
+ {
+ /* This is just a guess, but the -fps code wants to know how long
+ we were sleeping between frames.
+ */
+ long usecs = 1000000 * [self animationTimeInterval];
+ usecs -= 200; // caller apparently sleeps for slightly less sometimes...
+ if (usecs < 0) usecs = 0;
+ fps_slept (fpst, usecs);
+ }
+
+
+ /* Run any XtAppAddInput and XtAppAddTimeOut callbacks now.
+ Do this before delaying for next_frame_time to avoid throttling
+ timers to the hack's frame rate.
+ */
+ XtAppProcessEvent (XtDisplayToApplicationContext (xdpy),
+ XtIMTimer | XtIMAlternateInput);
+
+
+ /* It turns out that on some systems (possibly only 10.5 and older?)
+ [ScreenSaverView setAnimationTimeInterval] does nothing. This means
+ that we cannot rely on it.
+
+ Some of the screen hacks want to delay for long periods, and letting the
+ framework run the update function at 30 FPS when it really wanted half a
+ minute between frames would be bad. So instead, we assume that the
+ framework's animation timer might fire whenever, but we only invoke the
+ screen hack's "draw frame" method when enough time has expired.
+
+ This means two extra calls to gettimeofday() per frame. For fast-cycling
+ screen savers, that might actually slow them down. Oh well.
+
+ A side-effect of this is that it's not possible for a saver to request
+ an animation interval that is faster than animationTimeInterval.
+
+ HOWEVER! On modern systems where setAnimationTimeInterval is *not*
+ ignored, it's important that it be faster than 30 FPS. 240 FPS is good.
+
+ An NSTimer won't fire if the timer is already running the invocation
+ function from a previous firing. So, if we use a 30 FPS
+ animationTimeInterval (33333 µs) and a screenhack takes 40000 µs for a
+ frame, there will be a 26666 µs delay until the next frame, 66666 µs
+ after the beginning of the current frame. In other words, 25 FPS
+ becomes 15 FPS.
+
+ Frame rates tend to snap to values of 30/N, where N is a positive
+ integer, i.e. 30 FPS, 15 FPS, 10, 7.5, 6. And the 'snapped' frame rate
+ is rounded down from what it would normally be.
+
+ So if we set animationTimeInterval to 1/240 instead of 1/30, frame rates
+ become values of 60/N, 120/N, or 240/N, with coarser or finer frame rate
+ steps for higher or lower animation time intervals respectively.
+ */
+ struct timeval tv;
+ gettimeofday (&tv, 0);
+ double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ if (now < next_frame_time) return;
+
+ // [self flushBackbuffer];
+
+ if (resized_p) {
+ // We do this here instead of in setFrame so that all the
+ // Xlib drawing takes place under the animation timer.
+
+# ifndef USE_IPHONE
+ if (ogl_ctx)
+ [ogl_ctx setView:self];
+# endif // !USE_IPHONE
+
+ [self reshape_x11];
+ resized_p = NO;
+ }
+
+
+ // And finally:
+ //
+ // NSAssert(xdata, @"no xdata when drawing");
+ if (! xdata) abort();
+ unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
+ if (fpst && fps_cb)
+ fps_cb (xdpy, xwindow, fpst, xdata);
+
+ gettimeofday (&tv, 0);
+ now = tv.tv_sec + (tv.tv_usec / 1000000.0);
+ next_frame_time = now + (delay / 1000000.0);
+
+# ifdef JWXYZ_QUARTZ
+ [self drawBackbuffer];
+# endif
+ // This can also happen near the beginning of render_x11.
+ [self flushBackbuffer];
+
+# ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
+ if (delay < [self animationTimeInterval])
+ [self setAnimationTimeInterval:(delay / 1000000.0)];
+# endif
+
+# ifdef DO_GC_HACKERY
+ /* Current theory is that the 10.6 garbage collector sucks in the
+ following way:
+
+ It only does a collection when a threshold of outstanding
+ collectable allocations has been surpassed. However, CoreGraphics
+ creates lots of small collectable allocations that contain pointers
+ to very large non-collectable allocations: a small CG object that's
+ collectable referencing large malloc'd allocations (non-collectable)
+ containing bitmap data. So the large allocation doesn't get freed
+ until GC collects the small allocation, which triggers its finalizer
+ to run which frees the large allocation. So GC is deciding that it
+ doesn't really need to run, even though the process has gotten
+ enormous. GC eventually runs once pageouts have happened, but by
+ then it's too late, and the machine's resident set has been
+ sodomized.
+
+ So, we force an exhaustive garbage collection in this process
+ approximately every 5 seconds whether the system thinks it needs
+ one or not.
+ */
+ {
+ static int tick = 0;
+ if (++tick > 5*30) {
+ tick = 0;
+ objc_collect (OBJC_EXHAUSTIVE_COLLECTION);
+ }
+ }
+# endif // DO_GC_HACKERY
+
+# ifdef USE_IPHONE
+ }
+ @catch (NSException *e) {
+ [self handleException: e];
+ }
+# endif // USE_IPHONE
+}
+
+
+- (void) animateOneFrame
+{
+ // Render X11 into the backing store bitmap...
+
+# ifdef JWXYZ_QUARTZ
+ NSAssert (backbuffer, @"no back buffer");
+
+# ifdef USE_IPHONE
+ UIGraphicsPushContext (backbuffer);
+# endif
+# endif // JWXYZ_QUARTZ
+
+ [self render_x11];
+
+# if defined USE_IPHONE && defined JWXYZ_QUARTZ
+ UIGraphicsPopContext();
+# endif
+}
+
+
+# ifndef USE_IPHONE // Doesn't exist on iOS
+
+- (void) setFrame:(NSRect) newRect
+{
+ [super setFrame:newRect];
+
+ if (xwindow) // inform Xlib that the window has changed now.
+ [self resize_x11];
+}
+
+- (void) setFrameSize:(NSSize) newSize
+{
+ [super setFrameSize:newSize];
+ if (xwindow)
+ [self resize_x11];
+}
+
+# else // USE_IPHONE
+
+- (void) layoutSubviews
+{
+ [super layoutSubviews];
+ [self resizeGL];
+ if (xwindow)
+ [self resize_x11];
+}
+
+# endif
+
+
++(BOOL) performGammaFade
+{
+ return YES;
+}
+
+- (BOOL) hasConfigureSheet
+{
+ return YES;
+}
+
++ (NSString *) decompressXML: (NSData *)data
+{
+ if (! data) return 0;
+ BOOL compressed_p = !!strncmp ((const char *) data.bytes, "<?xml", 5);
+
+ // If it's not already XML, decompress it.
+ NSAssert (compressed_p, @"xml isn't compressed");
+ if (compressed_p) {
+ NSMutableData *data2 = 0;
+ int ret = -1;
+ z_stream zs;
+ memset (&zs, 0, sizeof(zs));
+ ret = inflateInit2 (&zs, 16 + MAX_WBITS);
+ if (ret == Z_OK) {
+ UInt32 usize = * (UInt32 *) (data.bytes + data.length - 4);
+ data2 = [NSMutableData dataWithLength: usize];
+ zs.next_in = (Bytef *) data.bytes;
+ zs.avail_in = (uint) data.length;
+ zs.next_out = (Bytef *) data2.bytes;
+ zs.avail_out = (uint) data2.length;
+ ret = inflate (&zs, Z_FINISH);
+ inflateEnd (&zs);
+ }
+ if (ret == Z_OK || ret == Z_STREAM_END)
+ data = data2;
+ else
+ NSAssert2 (0, @"gunzip error: %d: %s",
+ ret, (zs.msg ? zs.msg : "<null>"));
+ }
+
+ NSString *s = [[NSString alloc]
+ initWithData:data encoding:NSUTF8StringEncoding];
+ [s autorelease];
+ return s;
+}
+
+
+#ifndef USE_IPHONE
+- (NSWindow *) configureSheet
+#else
+- (UIViewController *) configureView
+#endif
+{
+ NSBundle *bundle = [NSBundle bundleForClass:[self class]];