-/* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski <jwz@jwz.org>
+/* xscreensaver, Copyright (c) 2006-2015 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
*/
#import <QuartzCore/QuartzCore.h>
+#import <sys/mman.h>
#import <zlib.h>
#import "XScreenSaverView.h"
#import "XScreenSaverConfigSheet.h"
#import "xlockmoreI.h"
#import "jwxyz-timers.h"
+#ifndef USE_IPHONE
+# import <OpenGL/glu.h>
+#endif
/* Garbage collection only exists if we are being compiled against the
10.6 SDK or newer, not if we are building against the 10.4 SDK.
# define DO_GC_HACKERY
#endif
+/* Duplicated in xlockmoreI.h and XScreenSaverGLView.m. */
+extern void clear_gl_error (void);
+extern void check_gl_error (const char *type);
+
extern struct xscreensaver_function_table *xscreensaver_function_table;
/* Global variables used by the screen savers
# endif
// ".textLiteral: ",
// ".textFile: ",
- ".textURL: http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
+ ".textURL: https://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss",
// ".textProgram: ",
".grabDesktopImages: yes",
# ifndef USE_IPHONE
next_frame_time = 0;
+# ifndef USE_IPHONE
+ // When the view fills the screen and double buffering is enabled, OS X will
+ // use page flipping for a minor CPU/FPS boost. In windowed mode, double
+ // buffering reduces the frame rate to 1/2 the screen's refresh rate.
+ double_buffered_p = !isPreview;
+# endif
+
# ifdef USE_IPHONE
double s = [self hackedContentScaleFactor];
# else
// So we can tell when we're docked.
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
-# endif // USE_IPHONE
-# ifdef USE_BACKBUFFER
- [self createBackbuffer:bb_size];
- [self initLayer];
-# endif
+ [self setBackgroundColor:[NSColor blackColor]];
+
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(didRotate:)
+ name:UIDeviceOrientationDidChangeNotification object:nil];
+# endif // USE_IPHONE
return self;
}
-- (void) initLayer
+
+#ifdef USE_IPHONE
++ (Class) layerClass
{
-# if !defined(USE_IPHONE) && defined(BACKBUFFER_CALAYER)
- [self setLayer: [CALayer layer]];
- self.layer.delegate = self;
- self.layer.opaque = YES;
- [self setWantsLayer: YES];
-# endif // !USE_IPHONE && BACKBUFFER_CALAYER
+ return [CAEAGLLayer class];
}
+#endif
- (id) initWithFrame:(NSRect)frame isPreview:(BOOL)p
- (void) dealloc
{
- NSAssert(![self isAnimating], @"still animating");
+ if ([self isAnimating])
+ [self stopAnimation];
NSAssert(!xdata, @"xdata not yet freed");
NSAssert(!xdpy, @"xdpy not yet freed");
+# ifdef USE_IPHONE
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+# endif
+
# ifdef USE_BACKBUFFER
- if (backbuffer)
- CGContextRelease (backbuffer);
+
+# ifdef BACKBUFFER_OPENGL
+ [ogl_ctx release];
+ // Releasing the OpenGL context should also free any OpenGL objects,
+ // including the backbuffer texture and frame/render/depthbuffers.
+# endif // BACKBUFFER_OPENGL
if (colorspace)
CGColorSpaceRelease (colorspace);
-# ifdef BACKBUFFER_CGCONTEXT
- if (window_ctx)
- CGContextRelease (window_ctx);
-# endif // BACKBUFFER_CGCONTEXT
-
# endif // USE_BACKBUFFER
[prefsReader release];
NSAssert(!initted_p && !xdata, @"already initialized");
// See comment in render_x11() for why this value is important:
- [self setAnimationTimeInterval: 1.0 / 120.0];
+ [self setAnimationTimeInterval: 1.0 / 240.0];
[super startAnimation];
/* We can't draw on the window from this method, so we actually do the
*/
# ifdef USE_IPHONE
+ {
+ CGSize b = self.bounds.size;
+ double s = [self hackedContentScaleFactor];
+ b.width *= s;
+ b.height *= s;
+ NSAssert (initial_bounds.width == b.width &&
+ initial_bounds.height == b.height,
+ @"bounds changed unexpectedly");
+ }
+
if (crash_timer)
[crash_timer invalidate];
[[UIApplication sharedApplication]
setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
# endif
-}
+#ifdef BACKBUFFER_OPENGL
+ CGSize new_backbuffer_size;
+
+ {
+# ifndef USE_IPHONE
+ if (!ogl_ctx) {
+
+ NSOpenGLPixelFormat *pixfmt = [self getGLPixelFormat];
+
+ NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat");
+
+ [pixfmt retain]; // #### ???
+
+ // Fun: On OS X 10.7, the second time an OpenGL context is created, after
+ // the preferences dialog is launched in SaverTester, the context only
+ // lasts until the first full GC. Then it turns black. Solution is to
+ // reuse the OpenGL context after this point.
+ ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt
+ shareContext:nil];
+
+ // Sync refreshes to the vertical blanking interval
+ GLint r = 1;
+ [ogl_ctx setValues:&r forParameter:NSOpenGLCPSwapInterval];
+// check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early?
+ }
+
+ [ogl_ctx makeCurrentContext];
+ check_gl_error ("makeCurrentContext");
+
+ // NSOpenGLContext logs an 'invalid drawable' when this is called
+ // from initWithFrame.
+ [ogl_ctx setView:self];
+
+ // Clear frame buffer ASAP, else there are bits left over from other apps.
+ glClearColor (0, 0, 0, 1);
+ glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+// glFinish ();
+// glXSwapBuffers (mi->dpy, mi->window);
+
+
+ // Enable multi-threading, if possible. This runs most OpenGL commands
+ // and GPU management on a second CPU.
+ {
+# ifndef kCGLCEMPEngine
+# define kCGLCEMPEngine 313 // Added in MacOS 10.4.8 + XCode 2.4.
+# endif
+ CGLContextObj cctx = CGLGetCurrentContext();
+ CGLError err = CGLEnable (cctx, kCGLCEMPEngine);
+ if (err != kCGLNoError) {
+ NSLog (@"enabling multi-threaded OpenGL failed: %d", err);
+ }
+ }
+
+ new_backbuffer_size = NSSizeToCGSize ([self bounds].size);
+
+# else // USE_IPHONE
+ if (!ogl_ctx) {
+ CAEAGLLayer *eagl_layer = (CAEAGLLayer *) self.layer;
+ eagl_layer.opaque = TRUE;
+ eagl_layer.drawableProperties = [self getGLProperties];
+
+ // Without this, the GL frame buffer is half the screen resolution!
+ eagl_layer.contentsScale = [UIScreen mainScreen].scale;
+
+ ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
+ }
+
+ [EAGLContext setCurrentContext: ogl_ctx];
+
+ CGSize screen_size = [[[UIScreen mainScreen] currentMode] size];
+ // iPad, simulator: 768x1024
+ // iPad, physical: 1024x768
+ if (screen_size.width > screen_size.height) {
+ CGFloat w = screen_size.width;
+ screen_size.width = screen_size.height;
+ screen_size.height = w;
+ }
+
+ if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer);
+ if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer);
+
+ glGenFramebuffersOES (1, &gl_framebuffer);
+ glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer);
+
+ glGenRenderbuffersOES (1, &gl_renderbuffer);
+ glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer);
+
+// redundant?
+// glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES,
+// (int)size.width, (int)size.height);
+ [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES
+ fromDrawable:(CAEAGLLayer*)self.layer];
+
+ glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
+ GL_RENDERBUFFER_OES, gl_renderbuffer);
+
+ [self addExtraRenderbuffers:screen_size];
+
+ int err = glCheckFramebufferStatusOES (GL_FRAMEBUFFER_OES);
+ switch (err) {
+ case GL_FRAMEBUFFER_COMPLETE_OES:
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_OES:
+ NSAssert (0, @"framebuffer incomplete attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_OES:
+ NSAssert (0, @"framebuffer incomplete missing attachment");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_OES:
+ NSAssert (0, @"framebuffer incomplete dimensions");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_OES:
+ NSAssert (0, @"framebuffer incomplete formats");
+ break;
+ case GL_FRAMEBUFFER_UNSUPPORTED_OES:
+ NSAssert (0, @"framebuffer unsupported");
+ break;
+/*
+ case GL_FRAMEBUFFER_UNDEFINED:
+ NSAssert (0, @"framebuffer undefined");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
+ NSAssert (0, @"framebuffer incomplete draw buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
+ NSAssert (0, @"framebuffer incomplete read buffer");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
+ NSAssert (0, @"framebuffer incomplete multisample");
+ break;
+ case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
+ NSAssert (0, @"framebuffer incomplete layer targets");
+ break;
+ */
+ default:
+ NSAssert (0, @"framebuffer incomplete, unknown error 0x%04X", err);
+ break;
+ }
+
+ glViewport (0, 0, screen_size.width, screen_size.height);
+
+ new_backbuffer_size = initial_bounds;
+
+# endif // USE_IPHONE
+
+ check_gl_error ("startAnimation");
+
+// NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR),
+// glGetString (GL_RENDERER), glGetString (GL_VERSION));
+
+ [self enableBackbuffer:new_backbuffer_size];
+ }
+#endif // BACKBUFFER_OPENGL
+
+#ifdef USE_BACKBUFFER
+ [self createBackbuffer:new_backbuffer_size];
+#endif
+}
- (void)stopAnimation
{
[[UIApplication sharedApplication]
setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
# endif
-}
+ // Without this, the GL frame stays on screen when switching tabs
+ // in System Preferences.
+ // (Or perhaps it used to. It doesn't seem to matter on 10.9.)
+ //
+# ifndef USE_IPHONE
+ [NSOpenGLContext clearCurrentContext];
+# endif // !USE_IPHONE
-/* Hook for the XScreenSaverGLView subclass
- */
-- (void) prepareContext
-{
+ clear_gl_error(); // This hack is defunct, don't let this linger.
+
+ CGContextRelease (backbuffer);
+ backbuffer = nil;
+
+ if (backbuffer_len)
+ munmap (backbuffer_data, backbuffer_len);
+ backbuffer_data = NULL;
+ backbuffer_len = 0;
}
-/* Hook for the XScreenSaverGLView subclass
- */
-- (void) resizeContext
+
+// #### maybe this could/should just be on 'lockFocus' instead?
+- (void) prepareContext
{
+ if (ogl_ctx) {
+#ifdef USE_IPHONE
+ [EAGLContext setCurrentContext:ogl_ctx];
+#else // !USE_IPHONE
+ [ogl_ctx makeCurrentContext];
+// check_gl_error ("makeCurrentContext");
+#endif // !USE_IPHONE
+ }
}
NSSize ssize = [[[UIScreen mainScreen] currentMode] size];
NSSize bsize = [self bounds].size;
+ CGFloat
+ max_ssize = ssize.width > ssize.height ? ssize.width : ssize.height,
+ max_bsize = bsize.width > bsize.height ? bsize.width : bsize.height;
+
// Ratio of screen size in pixels to view size in points.
- GLfloat s = ((ssize.width > ssize.height ? ssize.width : ssize.height) /
- (bsize.width > bsize.height ? bsize.width : bsize.height));
+ CGFloat s = max_ssize / max_bsize;
+
+ // Two constraints:
+
+ // 1. Don't exceed -- let's say 1280 pixels in either direction.
+ // (Otherwise the frame rate gets bad.)
+ CGFloat mag0 = ceil(max_ssize / 1280);
- if (ssize.width >= 1024 && ssize.height >= 1024)
- s = 1;
+ // 2. Don't let the pixel size get too small.
+ // (Otherwise pixels in IFS and similar are too fine.)
+ // So don't let the result be > 2 pixels per point.
+ CGFloat mag1 = ceil(s / 2);
- return s;
+ // As of iPhone 6, mag0 is always >= mag1. This may not be true in the future.
+ // (desired scale factor) = s / (desired magnification factor)
+ return s / (mag0 > mag1 ? mag0 : mag1);
}
rot_current_angle = f - rotation_ratio * dist;
// Intermediate frame size.
- rot_current_size.width = rot_from.width +
- rotation_ratio * (rot_to.width - rot_from.width);
- rot_current_size.height = rot_from.height +
- rotation_ratio * (rot_to.height - rot_from.height);
+ rot_current_size.width = floor(rot_from.width +
+ rotation_ratio * (rot_to.width - rot_from.width));
+ rot_current_size.height = floor(rot_from.height +
+ rotation_ratio * (rot_to.height - rot_from.height));
// Tick animation. Complete rotation in 1/6th sec.
double now = double_time();
#ifdef USE_BACKBUFFER
+# ifndef USE_IPHONE
+
+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);
+}
+
+# else
+
+static GLboolean
+gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string)
+{
+ size_t ext_len = strlen ((const char *)ext_name);
+
+ for (;;) {
+ const GLubyte *found = (const GLubyte *)strstr ((const char *)ext_string,
+ (const char *)ext_name);
+ if (!found)
+ break;
+
+ char last_ch = found[ext_len];
+ if ((found == ext_string || found[-1] == ' ') &&
+ (last_ch == ' ' || !last_ch)) {
+ return GL_TRUE;
+ }
+
+ ext_string = found + ext_len;
+ }
+
+ return GL_FALSE;
+}
+
+# endif
+
+/* Called during startAnimation before the first call to createBackbuffer. */
+- (void) enableBackbuffer:(CGSize)new_backbuffer_size
+{
+# 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 = 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
// Colorspaces and CGContexts only happen with non-GL hacks.
if (colorspace)
CGColorSpaceRelease (colorspace);
-# ifdef BACKBUFFER_CGCONTEXT
- if (window_ctx)
- CGContextRelease (window_ctx);
-# endif
+
+# 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];
-# if defined(BACKBUFFER_CGCONTEXT)
- // TODO: This was borrowed from jwxyz_window_resized, and should
- // probably be refactored.
-
- // Figure out which screen the window is currently on.
- CGDirectDisplayID cgdpy = 0;
-
- {
-// int wx, wy;
-// TODO: XTranslateCoordinates is returning (0,1200) on my system.
-// Is this right?
-// In any case, those weren't valid coordinates for CGGetDisplaysWithPoint.
-// XTranslateCoordinates (xdpy, xwindow, NULL, 0, 0, &wx, &wy, NULL);
-// p.x = wx;
-// p.y = wy;
-
- NSPoint p0 = {0, 0};
- p0 = [window convertBaseToScreen:p0];
- CGPoint p = {p0.x, p0.y};
- CGDisplayCount n;
- CGGetDisplaysWithPoint (p, 1, &cgdpy, &n);
- NSAssert (cgdpy, @"unable to find CGDisplay");
- }
-
- {
- // Figure out this screen's colorspace, and use that for every CGImage.
- //
- CMProfileRef profile = 0;
-
- // CMGetProfileByAVID is deprecated as of OS X 10.6, but there's no
- // documented replacement as of OS X 10.9.
- // http://lists.apple.com/archives/colorsync-dev/2012/Nov/msg00001.html
- CMGetProfileByAVID ((CMDisplayIDType) cgdpy, &profile);
- NSAssert (profile, @"unable to find colorspace profile");
- colorspace = CGColorSpaceCreateWithPlatformColorSpace (profile);
- NSAssert (colorspace, @"unable to find colorspace");
- }
-# elif defined(BACKBUFFER_CALAYER)
+# ifdef BACKBUFFER_OPENGL
// Was apparently faster until 10.9.
colorspace = CGColorSpaceCreateDeviceRGB ();
-# endif // BACKBUFFER_CALAYER
+# endif // BACKBUFFER_OPENGL
-# ifdef BACKBUFFER_CGCONTEXT
- window_ctx = [[window graphicsContext] graphicsPort];
- CGContextRetain (window_ctx);
-# endif // BACKBUFFER_CGCONTEXT
-
[self unlockFocus];
} else {
-# ifdef BACKBUFFER_CGCONTEXT
- window_ctx = NULL;
-# endif // BACKBUFFER_CGCONTEXT
colorspace = CGColorSpaceCreateDeviceRGB();
}
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.
backbuffer_size.width, backbuffer_size.height);
# endif
- backbuffer = CGBitmapContextCreate (NULL,
+ /* 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,
- (int)backbuffer_size.width * 4,
+ 8,
+ bytes_per_row,
colorspace,
- // kCGImageAlphaPremultipliedLast
- (kCGImageAlphaNoneSkipFirst |
- kCGBitmapByteOrder32Host)
- );
+ bitmap_info);
NSAssert (backbuffer, @"unable to allocate back buffer");
// Clear it.
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.
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
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
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. 120 FPS is good.
+ 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
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/120 instead of 1/30, frame rates
+ 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.
*/
gettimeofday (&tv, 0);
double now = tv.tv_sec + (tv.tv_usec / 1000000.0);
if (now < next_frame_time) return;
-
- [self prepareContext];
+
+ [self prepareContext]; // resize_x11 also calls this.
+ // [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.
- [self resizeContext];
+
+# ifndef USE_IPHONE
+ if (ogl_ctx)
+ [ogl_ctx setView:self];
+# endif // !USE_IPHONE
+
NSRect r;
# ifndef USE_BACKBUFFER
r = [self bounds];
// And finally:
//
-# ifndef USE_IPHONE
- NSDisableScreenUpdates();
-# endif
// NSAssert(xdata, @"no xdata when drawing");
if (! xdata) abort();
unsigned long delay = xsft->draw_cb (xdpy, xwindow, xdata);
if (fpst) xsft->fps_cb (xdpy, xwindow, fpst, xdata);
-# ifndef USE_IPHONE
- NSEnableScreenUpdates();
-# endif
gettimeofday (&tv, 0);
now = tv.tv_sec + (tv.tv_usec / 1000000.0);
next_frame_time = now + (delay / 1000000.0);
+ [self drawBackbuffer];
+
# ifdef USE_IPHONE // Allow savers on the iPhone to run full-tilt.
if (delay < [self animationTimeInterval])
[self setAnimationTimeInterval:(delay / 1000000.0)];
}
-/* drawRect always does nothing, and animateOneFrame renders bits to the
- screen. This is (now) true of both X11 and GL on both MacOS and iOS.
- */
-
-- (void)drawRect:(NSRect)rect
-{
- if (xwindow) // clear to the X window's bg color, not necessarily black.
- XClearWindow (xdpy, xwindow);
- else
- [super drawRect:rect]; // early: black.
-}
-
-
#ifndef USE_BACKBUFFER
- (void) animateOneFrame
[self render_x11];
-# ifdef USE_IPHONE
+# if defined USE_IPHONE && defined USE_BACKBUFFER
UIGraphicsPopContext();
# endif
-
-# ifdef USE_IPHONE
- // The rotation origin for layer.affineTransform is in the center already.
- CGAffineTransform t = ignore_rotation_p ?
- CGAffineTransformIdentity :
- CGAffineTransformMakeRotation (rot_current_angle / (180.0 / M_PI));
-
- // Ratio of backbuffer size in pixels to layer size in points.
- CGSize ssize = backbuffer_size;
- CGSize bsize = [self bounds].size;
- GLfloat s = ((ssize.width > ssize.height ? ssize.width : ssize.height) /
- (bsize.width > bsize.height ? bsize.width : bsize.height));
-
- self.layer.contentsScale = s;
- self.layer.affineTransform = t;
-
- /* Setting the layer's bounds also sets the view's bounds.
- The view's bounds must be in points, not pixels, and it
- must be rotated to the current orientation.
- */
- CGRect bounds;
- bounds.origin.x = 0;
- bounds.origin.y = 0;
- bounds.size.width = ssize.width / s;
- bounds.size.height = ssize.height / s;
- self.layer.bounds = bounds;
-
-# endif // USE_IPHONE
-
-# if defined(BACKBUFFER_CALAYER)
- [self.layer setNeedsDisplay];
-# elif defined(BACKBUFFER_CGCONTEXT)
- size_t
- w = CGBitmapContextGetWidth (backbuffer),
- h = CGBitmapContextGetHeight (backbuffer);
-
- size_t bpl = CGBitmapContextGetBytesPerRow (backbuffer);
- CGDataProviderRef prov = CGDataProviderCreateWithData (NULL,
- CGBitmapContextGetData(backbuffer),
- bpl * h,
- NULL);
-
-
- CGImageRef img = CGImageCreate (w, h,
- 8, 32,
- CGBitmapContextGetBytesPerRow(backbuffer),
- colorspace,
- CGBitmapContextGetBitmapInfo(backbuffer),
- prov, NULL, NO,
- kCGRenderingIntentDefault);
-
- CGDataProviderRelease (prov);
-
- CGRect rect;
- rect.origin.x = 0;
- rect.origin.y = 0;
- rect.size = backbuffer_size;
- CGContextDrawImage (window_ctx, rect, img);
-
- CGImageRelease (img);
-
- CGContextFlush (window_ctx);
-# endif // BACKBUFFER_CGCONTEXT
-}
-
-# ifdef BACKBUFFER_CALAYER
-
-- (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
-{
- // This "isn't safe" if NULL is passed to CGBitmapCreateContext before iOS 4.
- char *dest_data = (char *)CGBitmapContextGetData (ctx);
-
- // The CGContext here is normally upside-down on iOS.
- if (dest_data &&
- CGBitmapContextGetBitmapInfo (ctx) ==
- (kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host)
-# ifdef USE_IPHONE
- && CGContextGetCTM (ctx).d < 0
-# endif // USE_IPHONE
- )
- {
- size_t dest_height = CGBitmapContextGetHeight (ctx);
- size_t dest_bpr = CGBitmapContextGetBytesPerRow (ctx);
- size_t src_height = CGBitmapContextGetHeight (backbuffer);
- size_t src_bpr = CGBitmapContextGetBytesPerRow (backbuffer);
- char *src_data = (char *)CGBitmapContextGetData (backbuffer);
-
- size_t height = src_height < dest_height ? src_height : dest_height;
-
- if (src_bpr == dest_bpr) {
- // iPad 1: 4.0 ms, iPad 2: 6.7 ms
- memcpy (dest_data, src_data, src_bpr * height);
- } else {
- // iPad 1: 4.6 ms, iPad 2: 7.2 ms
- size_t bpr = src_bpr < dest_bpr ? src_bpr : dest_bpr;
- while (height) {
- memcpy (dest_data, src_data, bpr);
- --height;
- src_data += src_bpr;
- dest_data += dest_bpr;
- }
- }
- } else {
-
- // iPad 1: 9.6 ms, iPad 2: 12.1 ms
-
-# ifdef USE_IPHONE
- CGContextScaleCTM (ctx, 1, -1);
- CGFloat s = [self contentScaleFactor];
- CGFloat hs = [self hackedContentScaleFactor];
- CGContextTranslateCTM (ctx, 0, -backbuffer_size.height * hs / s);
-# endif // USE_IPHONE
-
- CGImageRef img = CGBitmapContextCreateImage (backbuffer);
- CGContextDrawImage (ctx, self.layer.bounds, img);
- CGImageRelease (img);
- }
}
-# endif // BACKBUFFER_CALAYER
#endif // USE_BACKBUFFER
case NSNextFunctionKey: k = XK_Next; break;
case NSBeginFunctionKey: k = XK_Begin; break;
case NSEndFunctionKey: k = XK_End; break;
+ case NSF1FunctionKey: k = XK_F1; break;
+ case NSF2FunctionKey: k = XK_F2; break;
+ case NSF3FunctionKey: k = XK_F3; break;
+ case NSF4FunctionKey: k = XK_F4; break;
+ case NSF5FunctionKey: k = XK_F5; break;
+ case NSF6FunctionKey: k = XK_F6; break;
+ case NSF7FunctionKey: k = XK_F7; break;
+ case NSF8FunctionKey: k = XK_F8; break;
+ case NSF9FunctionKey: k = XK_F9; break;
+ case NSF10FunctionKey: k = XK_F10; break;
+ case NSF11FunctionKey: k = XK_F11; break;
+ case NSF12FunctionKey: k = XK_F12; break;
default:
{
const char *s =
[super flagsChanged:e];
}
+
+- (NSOpenGLPixelFormat *) getGLPixelFormat
+{
+ NSAssert (prefsReader, @"no prefsReader for getGLPixelFormat");
+
+ NSOpenGLPixelFormatAttribute attrs[40];
+ int i = 0;
+ attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24;
+
+ if (double_buffered_p)
+ attrs[i++] = NSOpenGLPFADoubleBuffer;
+
+ attrs[i] = 0;
+
+ return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs];
+}
+
#else // USE_IPHONE
// }
[self resignFirstResponder];
- // Find SaverRunner.window (as opposed to SaverRunner.saverWindow)
- UIWindow *listWindow = 0;
- for (UIWindow *w in [[UIApplication sharedApplication] windows]) {
- if (w != [self window]) {
- listWindow = w;
- break;
- }
- }
-
- UIView *fader = [self superview]; // the "backgroundView" view is our parent
-
if (relaunch_p) { // Fake a shake on the SaverListController.
- UIViewController *v = [listWindow rootViewController];
- if ([v isKindOfClass: [UINavigationController class]]) {
-# if TARGET_IPHONE_SIMULATOR
- NSLog (@"simulating shake on saver list");
-# endif
- UINavigationController *n = (UINavigationController *) v;
- [[n topViewController] motionEnded: UIEventSubtypeMotionShake
- withEvent: nil];
- }
+ [_delegate didShake:self];
} else { // Not launching another, animate our return to the list.
# if TARGET_IPHONE_SIMULATOR
NSLog (@"fading back to saver list");
# endif
- UIWindow *saverWindow = [self window]; // not SaverRunner.window
- [listWindow setHidden:NO];
- [UIView animateWithDuration: 0.5
- animations:^{ fader.alpha = 0.0; }
- completion:^(BOOL finished) {
- [fader removeFromSuperview];
- fader.alpha = 1.0;
- [saverWindow setHidden:YES];
- [listWindow makeKeyAndVisible];
- [[[listWindow rootViewController] view] becomeFirstResponder];
- }];
+ [_delegate wantsFadeOut:self];
}
}
The savers are under a different UIWindow and a UINavigationController
that does not do automatic rotation.
- We have to do it this way for OpenGL savers because using Core Animation
- on an EAGLContext causes the OpenGL pipeline to fall back on software
- rendering and performance goes to hell.
-
- For X11-only savers, we could just use Core Animation and let the system
- handle it, but (maybe) it's simpler to do it the same way for X11 and GL.
+ We have to do it this way because using Core Animation on an EAGLContext
+ causes the OpenGL pipeline used on both X11 and GL savers to fall back on
+ software rendering and performance goes to hell.
During and after rotation, the size/shape of the X11 window changes,
and ConfigureNotify events are generated.
- X11 code (jwxyz) continues to draw into the (reshaped) backbuffer, which
- rotated at the last minute via a CGAffineTransformMakeRotation when it is
- copied to the display hardware.
+ X11 code (jwxyz) continues to draw into the (reshaped) backbuffer, which is
+ rendered onto a rotating OpenGL quad.
GL code always recieves a portrait-oriented X11 Window whose size never
changes. The GL COLOR_BUFFER is displayed on the hardware directly and
{
UIDeviceOrientation current = [[UIDevice currentDevice] orientation];
- /* If the simulator starts up in the rotated position, sometimes
- the UIDevice says we're in Portrait when we're not -- but it
- turns out that the UINavigationController knows what's up!
- So get it from there.
+ /* Sometimes UIDevice doesn't know the proper orientation, or the device is
+ face up/face down, so in those cases fall back to the status bar
+ orientation. The SaverViewController tries to set the status bar to the
+ proper orientation before it creates the XScreenSaverView; see
+ _storedOrientation in SaverViewController.
*/
- if (current == UIDeviceOrientationUnknown) {
- switch ([[[self window] rootViewController] interfaceOrientation]) {
- case UIInterfaceOrientationPortrait:
- current = UIDeviceOrientationPortrait;
- break;
- case UIInterfaceOrientationPortraitUpsideDown:
- current = UIDeviceOrientationPortraitUpsideDown;
- break;
- /* It's opposite day, "because rotating the device to the left requires
- rotating the content to the right" */
- case UIInterfaceOrientationLandscapeLeft:
- current = UIDeviceOrientationLandscapeRight;
- break;
- case UIInterfaceOrientationLandscapeRight:
- current = UIDeviceOrientationLandscapeLeft;
- break;
- default:
- break;
- }
+ if (current == UIDeviceOrientationUnknown ||
+ current == UIDeviceOrientationFaceUp ||
+ current == UIDeviceOrientationFaceDown) {
+ /* Mind the differences between UIInterfaceOrientation and
+ UIDeviceOrientaiton:
+ 1. UIInterfaceOrientation does not include FaceUp and FaceDown.
+ 2. LandscapeLeft and LandscapeRight are swapped between the two. But
+ converting between device and interface orientation doesn't need to
+ take this into account, because (from the UIInterfaceOrientation
+ description): "rotating the device requires rotating the content in
+ the opposite direction."
+ */
+ current = [UIApplication sharedApplication].statusBarOrientation;
}
/* On the iPad (but not iPhone 3GS, or the simulator) sometimes we get
if (rotation_ratio >= 0) return; // in the midst of rotation animation
if (orientation == current) return; // no change
- // When transitioning to FaceUp or FaceDown, pretend there was no change.
- if (current == UIDeviceOrientationFaceUp ||
- current == UIDeviceOrientationFaceDown)
- return;
-
new_orientation = current; // current animation target
rotation_ratio = 0; // start animating
rot_start_time = double_time();
}
# if TARGET_IPHONE_SIMULATOR
- NSLog (@"rotation begun: %s %d -> %s %d; %d x %d",
+ NSLog (@"%srotation begun: %s %d -> %s %d; %d x %d",
+ initted_p ? "" : "initial ",
orientname(orientation), (int) rot_current_angle,
orientname(new_orientation), (int) angle_to,
(int) rot_current_size.width, (int) rot_current_size.height);
# endif
+ // Even though the status bar isn't on the screen, this still does two things:
+ // 1. It fixes the orientation of the iOS simulator.
+ // 2. It places the iOS notification center on the expected edge.
+ // 3. It prevents the notification center from causing rotation events.
+ [[UIApplication sharedApplication] setStatusBarOrientation:new_orientation
+ animated:NO];
+
if (! initted_p) {
// If we've done a rotation but the saver hasn't been initialized yet,
// don't bother going through an X11 resize, but just do it now.
/* Given a mouse (touch) coordinate in unrotated, unscaled view coordinates,
convert it to what X11 and OpenGL expect.
+
+ Getting this crap right is tricky, given the confusion of the various
+ scale factors, so here's a checklist that I think covers all of the X11
+ and OpenGL cases. For each of these: rotate to all 4 orientations;
+ ensure the mouse tracks properly to all 4 corners.
+
+ Test it in Xcode 6, because Xcode 5.0.2 can't run the iPhone6+ simulator.
+
+ Test hacks must cover:
+ X11 ignoreRotation = true
+ X11 ignoreRotation = false
+ OpenGL (rotation is handled manually, so they never ignoreRotation)
+
+ Test devices must cover:
+ contentScaleFactor = 1, hackedContentScaleFactor = 1 (iPad 2)
+ contentScaleFactor = 2, hackedContentScaleFactor = 1 (iPad Retina Air)
+ contentScaleFactor = 2, hackedContentScaleFactor = 2 (iPhone 5 5s 6 6+)
+
+ iPad 2: 768x1024 / 1 = 768x1024
+ iPad Air: 1536x2048 / 2 = 768x1024 (iPad Retina is identical)
+ iPhone 4s: 640x960 / 2 = 320x480
+ iPhone 5: 640x1136 / 2 = 320x568 (iPhone 5s and iPhone 6 are identical)
+ iPhone 6+: 640x1136 / 2 = 320x568 (nativeBounds 960x1704 nativeScale 3)
+
+ Tests:
+ iPad2 iPadAir iPhone4s iPhone5 iPhone6+
+ Attraction X yes Y Y Y Y Y
+ Fireworkx X no Y Y Y Y Y
+ Carousel GL yes Y Y Y Y Y
+ Voronoi GL no Y Y Y Y Y
*/
- (void) convertMouse:(int)rot x:(int*)x y:(int *)y
{
- int w = [self frame].size.width;
- int h = [self frame].size.height;
int xx = *x, yy = *y;
- int swap;
- if (ignore_rotation_p) {
- // We need to rotate the coordinates to match the unrotated X11 window.
+# if TARGET_IPHONE_SIMULATOR
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (xdpy, xwindow, &xgwa);
+ NSLog (@"TOUCH %4d, %-4d in %4d x %-4d ig=%d rr=%d cs=%.0f hcs=%.0f\n",
+ *x, *y,
+ xgwa.width, xgwa.height,
+ ignore_rotation_p, [self reshapeRotatedWindow],
+ [self contentScaleFactor],
+ [self hackedContentScaleFactor]);
+ }
+# endif // TARGET_IPHONE_SIMULATOR
+
+ if (!ignore_rotation_p && [self reshapeRotatedWindow]) {
+ //
+ // For X11 hacks with ignoreRotation == false, we need to rotate the
+ // coordinates to match the unrotated X11 window. We do not do this
+ // for GL hacks, or for X11 hacks with ignoreRotation == true.
+ //
+ int w = [self frame].size.width;
+ int h = [self frame].size.height;
+ int swap;
switch (orientation) {
case UIDeviceOrientationLandscapeRight:
swap = xx; xx = h-yy; yy = swap;
}
}
- double s = [self contentScaleFactor];
+ double s = [self hackedContentScaleFactor];
*x = xx * s;
*y = yy * s;
-# if TARGET_IPHONE_SIMULATOR
- NSLog (@"touch %4d, %-4d in %4d x %-4d %d %d\n",
- *x, *y, (int)(w*s), (int)(h*s),
- ignore_rotation_p, [self reshapeRotatedWindow]);
-# endif
+# if TARGET_IPHONE_SIMULATOR || !defined __OPTIMIZE__
+ {
+ XWindowAttributes xgwa;
+ XGetWindowAttributes (xdpy, xwindow, &xgwa);
+ NSLog (@"touch %4d, %-4d in %4d x %-4d ig=%d rr=%d cs=%.0f hcs=%.0f\n",
+ *x, *y,
+ xgwa.width, xgwa.height,
+ ignore_rotation_p, [self reshapeRotatedWindow],
+ [self contentScaleFactor],
+ [self hackedContentScaleFactor]);
+ if (*x < 0 || *y < 0 || *x > xgwa.width || *y > xgwa.height)
+ abort();
+ }
+# endif // TARGET_IPHONE_SIMULATOR
}
}
}
+- (NSDictionary *)getGLProperties
+{
+ return [NSDictionary dictionaryWithObjectsAndKeys:
+ kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat,
+ nil];
+}
+
+- (void)addExtraRenderbuffers:(CGSize)size
+{
+ // No extra renderbuffers are needed for 2D screenhacks.
+}
+
#endif // USE_IPHONE