X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=9ab94bc48e26fd2b3b53557a783ce43afe4fb63e;hp=602629533f4c691cb90e5bc904b5cf08a9cf34a0;hb=d1ae2829ff0fd2a96c16a0c8c5420efaa47d7b30;hpb=7edd66e6bd3209013ee059819747b10b5835635b diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index 60262953..9ab94bc4 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2014 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2015 Jamie Zawinski * * Permission to use, copy, modify, distribute, and sell this software and its * documentation for any purpose is hereby granted without fee, provided that @@ -16,6 +16,7 @@ */ #import +#import #import #import "XScreenSaverView.h" #import "XScreenSaverConfigSheet.h" @@ -24,6 +25,9 @@ #import "xlockmoreI.h" #import "jwxyz-timers.h" +#ifndef USE_IPHONE +# import +#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. @@ -36,6 +40,10 @@ # 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 @@ -259,7 +267,7 @@ add_default_options (const XrmOptionDescRec *opts, # 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 @@ -416,6 +424,13 @@ orientname(unsigned long o) 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 @@ -436,25 +451,25 @@ orientname(unsigned long o) // 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 @@ -465,22 +480,26 @@ orientname(unsigned long o) - (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]; @@ -527,7 +546,7 @@ orientname(unsigned long o) 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 @@ -536,6 +555,16 @@ orientname(unsigned long o) */ # 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]; @@ -560,8 +589,165 @@ orientname(unsigned long o) [[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 { @@ -611,19 +797,38 @@ orientname(unsigned long o) [[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 + } } @@ -657,14 +862,27 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) 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); } @@ -692,10 +910,10 @@ double current_device_rotation (void) 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(); @@ -768,6 +986,180 @@ double current_device_rotation (void) #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 @@ -778,68 +1170,35 @@ double current_device_rotation (void) // 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(); } @@ -849,6 +1208,8 @@ double current_device_rotation (void) 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. @@ -858,16 +1219,112 @@ double current_device_rotation (void) backbuffer_size.width, backbuffer_size.height); # endif - backbuffer = CGBitmapContextCreate (NULL, + /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in + . + + 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. @@ -877,6 +1334,11 @@ double current_device_rotation (void) 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. @@ -889,9 +1351,124 @@ double current_device_rotation (void) 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 @@ -904,6 +1481,13 @@ double current_device_rotation (void) 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 @@ -1056,7 +1640,7 @@ double current_device_rotation (void) 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 @@ -1069,7 +1653,7 @@ double current_device_rotation (void) 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. */ @@ -1077,13 +1661,19 @@ double current_device_rotation (void) 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]; @@ -1107,21 +1697,17 @@ double current_device_rotation (void) // 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)]; @@ -1166,19 +1752,6 @@ double current_device_rotation (void) } -/* 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 @@ -1201,128 +1774,10 @@ double current_device_rotation (void) [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 @@ -1571,6 +2026,18 @@ double current_device_rotation (void) 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 = @@ -1662,6 +2129,23 @@ double current_device_rotation (void) [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 @@ -1682,42 +2166,13 @@ double current_device_rotation (void) // } [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]; } } @@ -1743,19 +2198,15 @@ double current_device_rotation (void) 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 @@ -1770,30 +2221,25 @@ double current_device_rotation (void) { 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 @@ -1808,11 +2254,6 @@ double current_device_rotation (void) 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(); @@ -1856,12 +2297,20 @@ double current_device_rotation (void) } # 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. @@ -1942,16 +2391,63 @@ double current_device_rotation (void) /* 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; @@ -1966,15 +2462,24 @@ double current_device_rotation (void) } } - 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 } @@ -2127,6 +2632,18 @@ double current_device_rotation (void) } } +- (NSDictionary *)getGLProperties +{ + return [NSDictionary dictionaryWithObjectsAndKeys: + kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, + nil]; +} + +- (void)addExtraRenderbuffers:(CGSize)size +{ + // No extra renderbuffers are needed for 2D screenhacks. +} + #endif // USE_IPHONE