X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverView.m;h=bc7f21a3e79221f2f682b23082e84994172c0c5d;hp=657835b9c9b1e5ebe9eeeb4e045cd616b2c75f6a;hb=aa75c7476aeaa84cf3abc192b376a8b03c325213;hpb=88cfe534a698a0562e81345957a50714af1453bc diff --git a/OSX/XScreenSaverView.m b/OSX/XScreenSaverView.m index 657835b9..bc7f21a3 100644 --- a/OSX/XScreenSaverView.m +++ b/OSX/XScreenSaverView.m @@ -1,4 +1,4 @@ -/* xscreensaver, Copyright (c) 2006-2015 Jamie Zawinski +/* xscreensaver, Copyright (c) 2006-2016 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 @@ -23,9 +23,15 @@ #import "Updater.h" #import "screenhackI.h" #import "xlockmoreI.h" +#import "jwxyzI.h" +#import "jwxyz-cocoa.h" #import "jwxyz-timers.h" -#ifndef USE_IPHONE +#ifdef USE_IPHONE +// XScreenSaverView.m speaks OpenGL ES just fine, but enableBackbuffer does +// need (jwzgles_)gluCheckExtension. +# import "jwzglesI.h" +#else # import #endif @@ -173,17 +179,16 @@ extern NSDictionary *make_function_table_dict(void); // ios-function-table.m const char *dir = [nsdir cStringUsingEncoding:NSUTF8StringEncoding]; const char *opath = getenv ("PATH"); if (!opath) opath = "/bin"; // $PATH is unset when running under Shark! - char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 30); - strcpy (npath, "PATH="); - strcat (npath, dir); + char *npath = (char *) malloc (strlen (opath) + strlen (dir) + 2); + strcpy (npath, dir); strcat (npath, ":"); strcat (npath, opath); - if (putenv (npath)) { - perror ("putenv"); - NSAssert1 (0, @"putenv \"%s\" failed", npath); + if (setenv ("PATH", npath, 1)) { + perror ("setenv"); + NSAssert1 (0, @"setenv \"PATH=%s\" failed", npath); } - /* Don't free (npath) -- MacOS's putenv() does not copy it. */ + free (npath); } @@ -196,14 +201,33 @@ extern NSDictionary *make_function_table_dict(void); // ios-function-table.m NSAssert1 (nsb, @"no bundle for class %@", [self class]); const char *s = [name cStringUsingEncoding:NSUTF8StringEncoding]; - char *env = (char *) malloc (strlen (s) + 40); - strcpy (env, "XSCREENSAVER_CLASSPATH="); - strcat (env, s); - if (putenv (env)) { - perror ("putenv"); - NSAssert1 (0, @"putenv \"%s\" failed", env); + if (setenv ("XSCREENSAVER_CLASSPATH", s, 1)) { + perror ("setenv"); + NSAssert1 (0, @"setenv \"XSCREENSAVER_CLASSPATH=%s\" failed", s); } - /* Don't free (env) -- MacOS's putenv() does not copy it. */ +} + + +- (void) loadCustomFonts +{ +# ifndef USE_IPHONE + NSBundle *nsb = [NSBundle bundleForClass:[self class]]; + NSMutableArray *fonts = [NSMutableArray arrayWithCapacity:20]; + for (NSString *ext in @[@"ttf", @"otf"]) { + [fonts addObjectsFromArray: [nsb pathsForResourcesOfType:ext + inDirectory:NULL]]; + } + for (NSString *font in fonts) { + CFURLRef url = (CFURLRef) [NSURL fileURLWithPath: font]; + CFErrorRef err = 0; + if (! CTFontManagerRegisterFontsForURL (url, kCTFontManagerScopeProcess, + &err)) { + // Just ignore errors: + // "The file has already been registered in the specified scope." + // NSLog (@"loading font: %@ %@", url, err); + } + } +# endif // !USE_IPHONE } @@ -257,7 +281,12 @@ add_default_options (const XrmOptionDescRec *opts, { 0, 0, 0, 0 } }; static const char *default_defaults [] = { + +# if defined(USE_IPHONE) && !defined(__OPTIMIZE__) + ".doFPS: True", +# else ".doFPS: False", +# endif ".doubleBuffer: True", ".multiSample: False", # ifndef USE_IPHONE @@ -346,42 +375,6 @@ add_default_options (const XrmOptionDescRec *opts, } -#ifdef USE_IPHONE -/* Returns the current time in seconds as a double. - */ -static double -double_time (void) -{ - struct timeval now; -# ifdef GETTIMEOFDAY_TWO_ARGS - struct timezone tzp; - gettimeofday(&now, &tzp); -# else - gettimeofday(&now); -# endif - - return (now.tv_sec + ((double) now.tv_usec * 0.000001)); -} -#endif // USE_IPHONE - -#if TARGET_IPHONE_SIMULATOR -static const char * -orientname(unsigned long o) -{ - switch (o) { - case UIDeviceOrientationUnknown: return "Unknown"; - case UIDeviceOrientationPortrait: return "Portrait"; - case UIDeviceOrientationPortraitUpsideDown: return "PortraitUpsideDown"; - case UIDeviceOrientationLandscapeLeft: return "LandscapeLeft"; - case UIDeviceOrientationLandscapeRight: return "LandscapeRight"; - case UIDeviceOrientationFaceUp: return "FaceUp"; - case UIDeviceOrientationFaceDown: return "FaceDown"; - default: return "ERROR"; - } -} -#endif // TARGET_IPHONE_SIMULATOR - - - (id) initWithFrame:(NSRect)frame saverName:(NSString *)saverName isPreview:(BOOL)isPreview @@ -409,7 +402,7 @@ orientname(unsigned long o) encoding:NSISOLatin1StringEncoding]; name = [@"org.jwz.xscreensaver." stringByAppendingString:name]; [self setResourcesEnv:name]; - + [self loadCustomFonts]; XrmOptionDescRec *opts = 0; const char **defs = 0; @@ -424,7 +417,7 @@ orientname(unsigned long o) next_frame_time = 0; -# ifndef USE_IPHONE +# if !defined USE_IPHONE && defined JWXYZ_QUARTZ // 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. @@ -432,32 +425,12 @@ orientname(unsigned long o) # endif # ifdef USE_IPHONE - double s = [self hackedContentScaleFactor]; -# else - double s = 1; -# endif - - CGSize bb_size; // pixels, not points - bb_size.width = s * frame.size.width; - bb_size.height = s * frame.size.height; - -# ifdef USE_IPHONE - initial_bounds = rot_current_size = rot_from = rot_to = bb_size; - rotation_ratio = -1; - - orientation = UIDeviceOrientationUnknown; - [self didRotate:nil]; [self initGestures]; // So we can tell when we're docked. [UIDevice currentDevice].batteryMonitoringEnabled = YES; [self setBackgroundColor:[NSColor blackColor]]; - - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(didRotate:) - name:UIDeviceOrientationDidChangeNotification object:nil]; # endif // USE_IPHONE return self; @@ -489,18 +462,23 @@ orientname(unsigned long o) [[NSNotificationCenter defaultCenter] removeObserver:self]; # endif -# ifdef USE_BACKBUFFER - # ifdef BACKBUFFER_OPENGL +# ifndef USE_IPHONE + [pixfmt release]; +# endif // !USE_IPHONE [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 defined JWXYZ_GL && defined USE_IPHONE + [ogl_ctx_pixmap release]; +# endif // JWXYZ_GL + +# ifdef JWXYZ_QUARTZ if (colorspace) CGColorSpaceRelease (colorspace); - -# endif // USE_BACKBUFFER +# endif // JWXYZ_QUARTZ [prefsReader release]; @@ -537,6 +515,45 @@ orientname(unsigned long o) [prefs setBool:YES forKey:@"wasRunning"]; [prefs synchronize]; } + + +- (void) resizeGL +{ + if (!ogl_ctx) + return; + + CGSize screen_size = self.bounds.size; + double s = self.contentScaleFactor; + screen_size.width *= s; + screen_size.height *= s; + +#if defined JWXYZ_GL + GLuint *framebuffer = &xwindow->gl_framebuffer; + GLuint *renderbuffer = &xwindow->gl_renderbuffer; + xwindow->window.current_drawable = xwindow; +#elif defined JWXYZ_QUARTZ + GLuint *framebuffer = &gl_framebuffer; + GLuint *renderbuffer = &gl_renderbuffer; +#endif // JWXYZ_QUARTZ + + if (*framebuffer) glDeleteFramebuffersOES (1, framebuffer); + if (*renderbuffer) glDeleteRenderbuffersOES (1, renderbuffer); + + create_framebuffer (framebuffer, 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, *renderbuffer); + + [self addExtraRenderbuffers:screen_size]; + + check_framebuffer_status(); +} #endif // USE_IPHONE @@ -555,16 +572,6 @@ 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]; @@ -590,6 +597,11 @@ orientname(unsigned long o) setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone]; # endif + xwindow = (Window) calloc (1, sizeof(*xwindow)); + xwindow->type = WINDOW; + xwindow->window.view = self; + CFRetain (xwindow->window.view); // needed for garbage collection? + #ifdef BACKBUFFER_OPENGL CGSize new_backbuffer_size; @@ -597,16 +609,16 @@ orientname(unsigned long o) # ifndef USE_IPHONE if (!ogl_ctx) { - NSOpenGLPixelFormat *pixfmt = [self getGLPixelFormat]; + pixfmt = [self getGLPixelFormat]; + [pixfmt retain]; 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. + // "Analyze" says that both pixfmt and ogl_ctx are leaked. ogl_ctx = [[NSOpenGLContext alloc] initWithFormat:pixfmt shareContext:nil]; @@ -623,6 +635,15 @@ orientname(unsigned long o) // from initWithFrame. [ogl_ctx setView:self]; + // This may not be necessary if there's FBO support. +# ifdef JWXYZ_GL + xwindow->window.pixfmt = pixfmt; + CFRetain (xwindow->window.pixfmt); + xwindow->window.virtual_screen = [ogl_ctx currentVirtualScreen]; + xwindow->window.current_drawable = xwindow; + NSAssert (ogl_ctx, @"no CGContext"); +# endif + // 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); @@ -655,86 +676,37 @@ orientname(unsigned long o) eagl_layer.contentsScale = [UIScreen mainScreen].scale; ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; - } +# ifdef JWXYZ_GL + ogl_ctx_pixmap = [[EAGLContext alloc] + initWithAPI:kEAGLRenderingAPIOpenGLES1 + sharegroup:ogl_ctx.sharegroup]; +# endif // JWXYZ_GL - [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; + eagl_layer.contentsGravity = [self getCAGravity]; } - if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer); - if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer); - - glGenFramebuffersOES (1, &gl_framebuffer); - glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer); +# ifdef JWXYZ_GL + xwindow->window.ogl_ctx_pixmap = ogl_ctx_pixmap; +# endif // JWXYZ_GL - 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; - } + [EAGLContext setCurrentContext: ogl_ctx]; - glViewport (0, 0, screen_size.width, screen_size.height); + [self resizeGL]; - new_backbuffer_size = initial_bounds; + double s = [self hackedContentScaleFactor]; + new_backbuffer_size = self.bounds.size; + new_backbuffer_size.width *= s; + new_backbuffer_size.height *= s; # endif // USE_IPHONE +# ifdef JWXYZ_GL + xwindow->ogl_ctx = ogl_ctx; +# ifndef USE_IPHONE + CFRetain (xwindow->ogl_ctx); +# endif // USE_IPHONE +# endif // JWXYZ_GL + check_gl_error ("startAnimation"); // NSLog (@"%s / %s / %s\n", glGetString (GL_VENDOR), @@ -744,9 +716,8 @@ orientname(unsigned long o) } #endif // BACKBUFFER_OPENGL -#ifdef USE_BACKBUFFER + [self setViewport]; [self createBackbuffer:new_backbuffer_size]; -#endif } - (void)stopAnimation @@ -767,10 +738,13 @@ orientname(unsigned long o) xsft->free_cb (xdpy, xwindow, xdata); [self unlockFocus]; - // xdpy must be freed before dealloc is called, because xdpy owns a - // circular reference to the parent XScreenSaverView. jwxyz_free_display (xdpy); xdpy = NULL; +# if defined JWXYZ_GL && !defined USE_IPHONE + CFRelease (xwindow->ogl_ctx); +# endif + CFRelease (xwindow->window.view); + free (xwindow); xwindow = NULL; // setup_p = NO; // #### wait, do we need this? @@ -808,6 +782,7 @@ orientname(unsigned long o) clear_gl_error(); // This hack is defunct, don't let this linger. +# ifdef JWXYZ_QUARTZ CGContextRelease (backbuffer); backbuffer = nil; @@ -815,19 +790,30 @@ orientname(unsigned long o) munmap (backbuffer_data, backbuffer_len); backbuffer_data = NULL; backbuffer_len = 0; +# endif +} + + +- (NSOpenGLContext *) oglContext +{ + return ogl_ctx; } // #### maybe this could/should just be on 'lockFocus' instead? - (void) prepareContext { - if (ogl_ctx) { + if (xwindow) { #ifdef USE_IPHONE [EAGLContext setCurrentContext:ogl_ctx]; #else // !USE_IPHONE [ogl_ctx makeCurrentContext]; // check_gl_error ("makeCurrentContext"); #endif // !USE_IPHONE + +#ifdef JWXYZ_GL + xwindow->window.current_drawable = xwindow; +#endif } } @@ -859,21 +845,20 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) */ - (CGFloat) hackedContentScaleFactor { - 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. - CGFloat s = max_ssize / max_bsize; + CGFloat s = self.contentScaleFactor; // 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); + // Actually let's make that 1440 since iPhone 6 is natively 1334. + CGFloat mag0 = ceil(max_bsize * s / 1440); // 2. Don't let the pixel size get too small. // (Otherwise pixels in IFS and similar are too fine.) @@ -886,72 +871,39 @@ screenhack_do_fps (Display *dpy, Window w, fps_state *fpst, void *closure) } -static GLfloat _global_rot_current_angle_kludge; - -double current_device_rotation (void) -{ - return -_global_rot_current_angle_kludge; -} - - -- (void) hackRotation +double +current_device_rotation (void) { - if (rotation_ratio >= 0) { // in the midst of a rotation animation - -# define CLAMP180(N) while (N < 0) N += 360; while (N > 180) N -= 360 - GLfloat f = angle_from; - GLfloat t = angle_to; - CLAMP180(f); - CLAMP180(t); - GLfloat dist = -(t-f); - CLAMP180(dist); - - // Intermediate angle. - rot_current_angle = f - rotation_ratio * dist; - - // Intermediate frame size. - 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(); - double duration = 1/6.0; - rotation_ratio = 1 - ((rot_start_time + duration - now) / duration); + UIDeviceOrientation o = [[UIDevice currentDevice] orientation]; - if (rotation_ratio > 1 || ignore_rotation_p) { // Done animating. - orientation = new_orientation; - rot_current_angle = angle_to; - rot_current_size = rot_to; - rotation_ratio = -1; - -# if TARGET_IPHONE_SIMULATOR - NSLog (@"rotation ended: %s %d, %d x %d", - orientname(orientation), (int) rot_current_angle, - (int) rot_current_size.width, (int) rot_current_size.height); -# endif - - // Check orientation again in case we rotated again while rotating: - // this is a no-op if nothing has changed. - [self didRotate:nil]; - } - } else { // Not animating a rotation. - rot_current_angle = angle_to; - rot_current_size = rot_to; + /* 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 (o == UIDeviceOrientationUnknown || + o == UIDeviceOrientationFaceUp || + o == UIDeviceOrientationFaceDown) { + /* Mind the differences between UIInterfaceOrientation and + UIDeviceOrientation: + 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." + */ + /* statusBarOrientation deprecated in iOS 9 */ + o = [UIApplication sharedApplication].statusBarOrientation; } - CLAMP180(rot_current_angle); - _global_rot_current_angle_kludge = rot_current_angle; - -# undef CLAMP180 - - CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow]) - ? initial_bounds - : rot_current_size); - if ((int) backbuffer_size.width != (int) rotsize.width || - (int) backbuffer_size.height != (int) rotsize.height) - [self resize_x11]; + switch (o) { + case UIDeviceOrientationLandscapeLeft: return -90; break; + case UIDeviceOrientationLandscapeRight: return 90; break; + case UIDeviceOrientationPortraitUpsideDown: return 180; break; + default: return 0; break; + } } @@ -984,7 +936,7 @@ double current_device_rotation (void) #endif // USE_IPHONE -#ifdef USE_BACKBUFFER +#ifdef JWXYZ_QUARTZ # ifndef USE_IPHONE @@ -1004,31 +956,6 @@ gl_check_ver (const struct gl_version *caps, (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. */ @@ -1065,9 +992,8 @@ gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string) ? 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_limited_npot_p = jwzgles_gluCheckExtension + ((const GLubyte *) "GL_APPLE_texture_2D_limited_npot", extensions); gl_texture_target = GL_TEXTURE_2D; # endif @@ -1097,8 +1023,11 @@ gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string) // 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; + 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 @@ -1121,16 +1050,6 @@ gluCheckExtension (const GLubyte *ext_name, const GLubyte *ext_string) 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"); } @@ -1160,6 +1079,60 @@ to_pow2 (size_t x) } +#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 @@ -1171,22 +1144,6 @@ to_pow2 (size_t x) if (colorspace) CGColorSpaceRelease (colorspace); -# ifdef BACKBUFFER_OPENGL - NSAssert ([NSOpenGLContext currentContext] == - ogl_ctx, @"invalid GL context"); - - // This almost isn't necessary, except for the ugly aliasing artifacts. -# ifndef USE_IPHONE - glViewport (0, 0, new_size.width, new_size.height); - - glMatrixMode (GL_PROJECTION); - glLoadIdentity(); - // This is pretty similar to the glOrthof in enableBackbuffer for iPhone. - glOrtho (-new_size.width, new_size.width, -new_size.height, new_size.height, - -1, 1); -# endif // !USE_IPHONE -# endif // BACKBUFFER_OPENGL - NSWindow *window = [self window]; if (window && xdpy) { @@ -1202,21 +1159,24 @@ to_pow2 (size_t x) colorspace = CGColorSpaceCreateDeviceRGB(); } + CGSize osize = CGSizeZero; + if (backbuffer) { + osize.width = CGBitmapContextGetWidth(backbuffer); + osize.height = CGBitmapContextGetHeight(backbuffer); + } + if (backbuffer && - (int)backbuffer_size.width == (int)new_size.width && - (int)backbuffer_size.height == (int)new_size.height) + (int)osize.width == (int)new_size.width && + (int)osize.height == (int)new_size.height) return; CGContextRef ob = backbuffer; void *odata = backbuffer_data; size_t olen = backbuffer_len; - CGSize osize = backbuffer_size; // pixels, not points. - backbuffer_size = new_size; // pixels, not points. - -# if TARGET_IPHONE_SIMULATOR +# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR NSLog(@"backbuffer %.0fx%.0f", - backbuffer_size.width, backbuffer_size.height); + new_size.width, new_size.height); # endif /* OS X uses APPLE_client_storage and APPLE_texture_range, as described in @@ -1245,8 +1205,8 @@ to_pow2 (size_t x) */ backbuffer_data = NULL; - gl_texture_w = (int)backbuffer_size.width; - gl_texture_h = (int)backbuffer_size.height; + gl_texture_w = (int)new_size.width; + gl_texture_h = (int)new_size.height; NSAssert (gl_texture_target == GL_TEXTURE_2D # ifndef USE_IPHONE @@ -1319,8 +1279,8 @@ to_pow2 (size_t x) (order_little_p ? kCGBitmapByteOrder32Little : kCGBitmapByteOrder32Big); backbuffer = CGBitmapContextCreate (backbuffer_data, - (int)backbuffer_size.width, - (int)backbuffer_size.height, + (int)new_size.width, + (int)new_size.height, 8, bytes_per_row, colorspace, @@ -1330,7 +1290,7 @@ to_pow2 (size_t x) // Clear it. CGRect r; r.origin.x = r.origin.y = 0; - r.size = backbuffer_size; + r.size = new_size; CGContextSetGrayFillColor (backbuffer, 0, 1); CGContextFillRect (backbuffer, r); @@ -1344,7 +1304,7 @@ to_pow2 (size_t x) CGRect rect; // pixels, not points rect.origin.x = 0; - rect.origin.y = (backbuffer_size.height - osize.height); + rect.origin.y = (new_size.height - osize.height); rect.size = osize; CGImageRef img = CGBitmapContextCreateImage (ob); @@ -1381,51 +1341,28 @@ to_pow2 (size_t x) 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 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) { - 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 + if (gl_texture_target != GL_TEXTURE_RECTANGLE_EXT) # 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; + w /= gl_texture_w; + h /= gl_texture_h; } -# 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 + 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); @@ -1434,29 +1371,81 @@ to_pow2 (size_t x) # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR check_gl_error ("drawBackbuffer"); # endif +# endif // BACKBUFFER_OPENGL +} - // This can also happen near the beginning of render_x11. - [self flushBackbuffer]; +#endif // JWXYZ_QUARTZ -# endif // BACKBUFFER_OPENGL +#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 +# 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 +# endif // USE_IPHONE # if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR // glGetError waits for the OpenGL command pipe to flush, so skip it in @@ -1469,18 +1458,40 @@ to_pow2 (size_t x) } -#endif // USE_BACKBUFFER - - /* Inform X11 that the size of our window has changed. */ - (void) resize_x11 { - if (!xwindow) return; // early + 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. + // + if ([self ignoreRotation] && new_size.width > new_size.height) { + 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 - CGSize new_size; // pixels, not points + [self setViewport]; -# ifdef USE_BACKBUFFER + // On first resize, xwindow->frame is 0x0. + if (xwindow->frame.width == new_size.width && + xwindow->frame.height == new_size.height) + return; [self prepareContext]; @@ -1488,26 +1499,26 @@ to_pow2 (size_t x) [ogl_ctx update]; # endif // BACKBUFFER_OPENGL && !USE_IPHONE -# ifdef USE_IPHONE - CGSize rotsize = ((ignore_rotation_p || ![self reshapeRotatedWindow]) - ? initial_bounds - : rot_current_size); - new_size.width = rotsize.width; - new_size.height = rotsize.height; -# else // !USE_IPHONE - new_size = NSSizeToCGSize([self bounds].size); -# endif // !USE_IPHONE - - [self createBackbuffer:new_size]; - jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height, - backbuffer); -# else // !USE_BACKBUFFER - new_size = [self bounds].size; - jwxyz_window_resized (xdpy, xwindow, 0, 0, new_size.width, new_size.height, - 0); -# endif // !USE_BACKBUFFER + 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; -# if TARGET_IPHONE_SIMULATOR + [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 @@ -1516,31 +1527,60 @@ to_pow2 (size_t x) } +#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 { - - if (orientation == UIDeviceOrientationUnknown) - [self didRotate:nil]; - [self hackRotation]; # endif + // jwxyz_make_display needs this. + [self prepareContext]; // resize_x11 also calls this. + if (!initted_p) { if (! xdpy) { -# ifdef USE_BACKBUFFER - NSAssert (backbuffer, @"no back buffer"); - xdpy = jwxyz_make_display (self, backbuffer); -# else - xdpy = jwxyz_make_display (self, 0); -# endif - xwindow = XRootWindow (xdpy, 0); +# ifdef JWXYZ_QUARTZ + xwindow->cgc = backbuffer; +# endif // JWXYZ_QUARTZ + xdpy = jwxyz_make_display (xwindow); -# ifdef USE_IPHONE +# if defined USE_IPHONE /* Some X11 hacks (fluidballs) want to ignore all rotation events. */ - ignore_rotation_p = + _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]; @@ -1598,6 +1638,11 @@ to_pow2 (size_t x) xsft->fps_cb = 0; } +# ifdef USE_IPHONE + if (current_device_rotation() != 0) // launched while rotated + resized_p = YES; +# endif + [self checkForUpdates]; } @@ -1623,6 +1668,14 @@ to_pow2 (size_t x) } + /* 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. @@ -1662,7 +1715,6 @@ to_pow2 (size_t x) double now = tv.tv_sec + (tv.tv_usec / 1000000.0); if (now < next_frame_time) return; - [self prepareContext]; // resize_x11 also calls this. // [self flushBackbuffer]; if (resized_p) { @@ -1674,26 +1726,10 @@ to_pow2 (size_t x) [ogl_ctx setView:self]; # endif // !USE_IPHONE - NSRect r; -# ifndef USE_BACKBUFFER - r = [self bounds]; -# else // USE_BACKBUFFER - r.origin.x = 0; - r.origin.y = 0; - r.size.width = backbuffer_size.width; - r.size.height = backbuffer_size.height; -# endif // USE_BACKBUFFER - - xsft->reshape_cb (xdpy, xwindow, xdata, r.size.width, r.size.height); + [self reshape_x11]; resized_p = NO; } - // Run any XtAppAddInput callbacks now. - // (Note that XtAppAddTimeOut callbacks have already been run by - // the Cocoa event loop.) - // - jwxyz_sources_run (display_sources_data (xdpy)); - // And finally: // @@ -1707,7 +1743,11 @@ to_pow2 (size_t x) 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]) @@ -1753,36 +1793,27 @@ to_pow2 (size_t x) } -#ifndef USE_BACKBUFFER - -- (void) animateOneFrame -{ - [self render_x11]; - jwxyz_flush_context(xdpy); -} - -#else // USE_BACKBUFFER - - (void) animateOneFrame { // Render X11 into the backing store bitmap... +# ifdef JWXYZ_QUARTZ NSAssert (backbuffer, @"no back buffer"); -# ifdef USE_IPHONE +# ifdef USE_IPHONE UIGraphicsPushContext (backbuffer); -# endif +# endif +# endif // JWXYZ_QUARTZ [self render_x11]; -# if defined USE_IPHONE && defined USE_BACKBUFFER +# if defined USE_IPHONE && defined JWXYZ_QUARTZ UIGraphicsPopContext(); # endif } -#endif // USE_BACKBUFFER - +# ifndef USE_IPHONE // Doesn't exist on iOS - (void) setFrame:(NSRect) newRect { @@ -1792,15 +1823,24 @@ to_pow2 (size_t x) [self resize_x11]; } - -# ifndef USE_IPHONE // Doesn't exist on iOS - (void) setFrameSize:(NSSize) newSize { [super setFrameSize:newSize]; if (xwindow) [self resize_x11]; } -# endif // !USE_IPHONE + +# else // USE_IPHONE + +- (void) layoutSubviews +{ + [super layoutSubviews]; + [self resizeGL]; + if (xwindow) + [self resize_x11]; +} + +# endif +(BOOL) performGammaFade @@ -1843,7 +1883,10 @@ to_pow2 (size_t x) ret, (zs.msg ? zs.msg : "")); } - return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + NSString *s = [[NSString alloc] + initWithData:data encoding:NSUTF8StringEncoding]; + [s autorelease]; + return s; } @@ -2139,12 +2182,57 @@ to_pow2 (size_t x) int i = 0; attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; +/* OpenGL's core profile removes a lot of the same stuff that was removed in + OpenGL ES (e.g. glBegin, glDrawPixels), so it might be a possibility. + + opengl_core_p = True; + if (opengl_core_p) { + attrs[i++] = NSOpenGLPFAOpenGLProfile; + attrs[i++] = NSOpenGLProfileVersion3_2Core; + } + */ + +/* Eventually: multisampled pixmaps. May not be supported everywhere. + if (multi_sample_p) { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6; + } + */ + +# ifdef JWXYZ_QUARTZ + // Under Quartz, we're just blitting a texture. if (double_buffered_p) attrs[i++] = NSOpenGLPFADoubleBuffer; +# endif + +# ifdef JWXYZ_GL + /* Under OpenGL, all sorts of drawing commands are being issued, and it might + be a performance problem if this activity occurs on the front buffer. + Also, some screenhacks expect OS X/iOS to always double-buffer. + NSOpenGLPFABackingStore prevents flickering with screenhacks that + don't redraw the entire screen every frame. + */ + attrs[i++] = NSOpenGLPFADoubleBuffer; + attrs[i++] = NSOpenGLPFABackingStore; +# endif + + attrs[i++] = NSOpenGLPFAWindow; +# ifdef JWXYZ_GL + attrs[i++] = NSOpenGLPFAPixelBuffer; + /* ...But not NSOpenGLPFAFullScreen, because that would be for + [NSOpenGLContext setFullScreen]. + */ +# endif + + /* NSOpenGLPFAFullScreen would go here if initWithFrame's isPreview == NO. + */ attrs[i] = 0; - return [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + NSOpenGLPixelFormat *p = [[NSOpenGLPixelFormat alloc] + initWithAttributes:attrs]; + [p autorelease]; + return p; } #else // USE_IPHONE @@ -2170,7 +2258,7 @@ to_pow2 (size_t x) if (relaunch_p) { // Fake a shake on the SaverListController. [_delegate didShake:self]; } else { // Not launching another, animate our return to the list. -# if TARGET_IPHONE_SIMULATOR +# if !defined __OPTIMIZE__ || TARGET_IPHONE_SIMULATOR NSLog (@"fading back to saver list"); # endif [_delegate wantsFadeOut:self]; @@ -2178,149 +2266,6 @@ to_pow2 (size_t x) } -/* Whether the shape of the X11 Window should be changed to HxW when the - device is in a landscape orientation. X11 hacks want this, but OpenGL - hacks do not. - */ -- (BOOL)reshapeRotatedWindow -{ - return YES; -} - - -/* Called after the device's orientation has changed. - - Rotation is complicated: the UI, X11 and OpenGL work in 3 different ways. - - The UI (list of savers, preferences panels) is rotated by the system, - because its UIWindow is under a UINavigationController that does - automatic rotation, using Core Animation. - - The savers are under a different UIWindow and a UINavigationController - that does not do automatic rotation. - - 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 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 - unrotated, so the GL hacks themselves are responsible for rotating the - GL scene to match current_device_rotation(). - - Touch events are converted to mouse clicks, and those XEvent coordinates - are reported in the coordinate system currently in use by the X11 window. - Again, GL must convert those. - */ -- (void)didRotate:(NSNotification *)notification -{ - UIDeviceOrientation current = [[UIDevice currentDevice] orientation]; - - /* 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 || - 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 - an orientation change event with an unknown orientation. Those seem - to always be immediately followed by another orientation change with - a *real* orientation change, so let's try just ignoring those bogus - ones and hoping that the real one comes in shortly... - */ - if (current == UIDeviceOrientationUnknown) - return; - - if (rotation_ratio >= 0) return; // in the midst of rotation animation - if (orientation == current) return; // no change - - new_orientation = current; // current animation target - rotation_ratio = 0; // start animating - rot_start_time = double_time(); - - switch (orientation) { - case UIDeviceOrientationLandscapeLeft: angle_from = 90; break; - case UIDeviceOrientationLandscapeRight: angle_from = 270; break; - case UIDeviceOrientationPortraitUpsideDown: angle_from = 180; break; - default: angle_from = 0; break; - } - - switch (new_orientation) { - case UIDeviceOrientationLandscapeLeft: angle_to = 90; break; - case UIDeviceOrientationLandscapeRight: angle_to = 270; break; - case UIDeviceOrientationPortraitUpsideDown: angle_to = 180; break; - default: angle_to = 0; break; - } - - switch (orientation) { - case UIDeviceOrientationLandscapeRight: // from landscape - case UIDeviceOrientationLandscapeLeft: - rot_from.width = initial_bounds.height; - rot_from.height = initial_bounds.width; - break; - default: // from portrait - rot_from.width = initial_bounds.width; - rot_from.height = initial_bounds.height; - break; - } - - switch (new_orientation) { - case UIDeviceOrientationLandscapeRight: // to landscape - case UIDeviceOrientationLandscapeLeft: - rot_to.width = initial_bounds.height; - rot_to.height = initial_bounds.width; - break; - default: // to portrait - rot_to.width = initial_bounds.width; - rot_to.height = initial_bounds.height; - break; - } - -# if TARGET_IPHONE_SIMULATOR - 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. - rot_start_time = 0; // dawn of time - [self hackRotation]; - } -} - - /* We distinguish between taps and drags. - Drags/pans (down, motion, up) are sent to the saver to handle. @@ -2418,66 +2363,85 @@ to_pow2 (size_t x) 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 + Attraction X yes - - - - Y + Fireworkx X no - - - - Y + Carousel GL yes - - - - Y + Voronoi GL no - - - - - */ -- (void) convertMouse:(int)rot x:(int*)x y:(int *)y +- (void) convertMouse:(CGPoint *)p { - int xx = *x, yy = *y; + CGFloat xx = p->x, yy = p->y; # 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, + NSLog (@"TOUCH %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n", + p->x, p->y, xgwa.width, xgwa.height, - ignore_rotation_p, [self reshapeRotatedWindow], [self contentScaleFactor], - [self hackedContentScaleFactor]); + [self hackedContentScaleFactor], + [self rotateTouches], [self ignoreRotation]); } # endif // TARGET_IPHONE_SIMULATOR - if (!ignore_rotation_p && [self reshapeRotatedWindow]) { + if ([self rotateTouches]) { + + // The XScreenSaverGLView case: + // The X11 window is rotated, as is the framebuffer. + // The device coordinates match the framebuffer dimensions, + // but might have axes swapped... and we need to swap them + // by ratios. // - // 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; + GLfloat xr = (GLfloat) xx / w; + GLfloat yr = (GLfloat) yy / h; + GLfloat swap; + int o = (int) current_device_rotation(); + switch (o) { + case -90: case 270: swap = xr; xr = 1-yr; yr = swap; break; + case 90: case -270: swap = xr; xr = yr; yr = 1-swap; break; + case 180: case -180: xr = 1-xr; yr = 1-yr; break; + default: break; + } + xx = xr * w; + yy = yr * h; + + } else if ([self ignoreRotation]) { + + // The X11 case, where the hack has opted not to rotate: + // The X11 window is unrotated, but the framebuffer is rotated. + // The device coordinates match the framebuffer, so they need to + // be de-rotated to match the X11 window. // 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; - break; - case UIDeviceOrientationLandscapeLeft: - swap = xx; xx = yy; yy = w-swap; - break; - case UIDeviceOrientationPortraitUpsideDown: - xx = w-xx; yy = h-yy; - default: - break; + int o = (int) current_device_rotation(); + switch (o) { + case -90: case 270: swap = xx; xx = h-yy; yy = swap; break; + case 90: case -270: swap = xx; xx = yy; yy = w-swap; break; + case 180: case -180: xx = w-xx; yy = h-yy; break; + default: break; } } double s = [self hackedContentScaleFactor]; - *x = xx * s; - *y = yy * s; + p->x = xx * s; + p->y = yy * s; # 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, + NSLog (@"touch %4g, %-4g in %4d x %-4d cs=%.0f hcs=%.0f r=%d ig=%d\n", + p->x, p->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) + [self hackedContentScaleFactor], + [self rotateTouches], [self ignoreRotation]); + if (p->x < 0 || p->y < 0 || p->x > xgwa.width || p->y > xgwa.height) abort(); } # endif // TARGET_IPHONE_SIMULATOR @@ -2520,30 +2484,30 @@ to_pow2 (size_t x) memset (&xe, 0, sizeof(xe)); CGPoint p = [sender locationInView:self]; // this is in points, not pixels - int x = p.x; - int y = p.y; - [self convertMouse: rot_current_angle x:&x y:&y]; - jwxyz_mouse_moved (xdpy, xwindow, x, y); + [self convertMouse:&p]; + NSAssert (xwindow && xwindow->type == WINDOW, @"not a window"); + xwindow->window.last_mouse_x = p.x; + xwindow->window.last_mouse_y = p.y; switch (sender.state) { case UIGestureRecognizerStateBegan: xe.xany.type = ButtonPress; xe.xbutton.button = 1; - xe.xbutton.x = x; - xe.xbutton.y = y; + xe.xbutton.x = p.x; + xe.xbutton.y = p.y; break; case UIGestureRecognizerStateEnded: xe.xany.type = ButtonRelease; xe.xbutton.button = 1; - xe.xbutton.x = x; - xe.xbutton.y = y; + xe.xbutton.x = p.x; + xe.xbutton.y = p.y; break; case UIGestureRecognizerStateChanged: xe.xany.type = MotionNotify; - xe.xmotion.x = x; - xe.xmotion.y = y; + xe.xmotion.x = p.x; + xe.xmotion.y = p.y; break; default: @@ -2579,14 +2543,12 @@ to_pow2 (size_t x) memset (&xe, 0, sizeof(xe)); CGPoint p = [sender locationInView:self]; // this is in points, not pixels - int x = p.x; - int y = p.y; - [self convertMouse: rot_current_angle x:&x y:&y]; + [self convertMouse:&p]; - if (abs(x) > abs(y)) - xe.xkey.keycode = (x > 0 ? XK_Right : XK_Left); + if (fabs(p.x) > fabs(p.y)) + xe.xkey.keycode = (p.x > 0 ? XK_Right : XK_Left); else - xe.xkey.keycode = (y > 0 ? XK_Down : XK_Up); + xe.xkey.keycode = (p.y > 0 ? XK_Down : XK_Up); BOOL ok1 = [self sendEvent: &xe]; xe.xany.type = KeyRelease; @@ -2637,6 +2599,12 @@ to_pow2 (size_t x) { return [NSDictionary dictionaryWithObjectsAndKeys: kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, +# ifdef JWXYZ_GL + /* This could be disabled if we knew the screen would be redrawn + entirely for every frame. + */ + [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, +# endif // JWXYZ_GL nil]; } @@ -2644,6 +2612,13 @@ to_pow2 (size_t x) { // No extra renderbuffers are needed for 2D screenhacks. } + + +- (NSString *)getCAGravity +{ + return kCAGravityCenter; // Looks better in e.g. Compass. +// return kCAGravityBottomLeft; +} #endif // USE_IPHONE @@ -2706,7 +2681,7 @@ to_pow2 (size_t x) options:(NSWorkspaceLaunchWithoutAddingToRecents | NSWorkspaceLaunchWithoutActivation | NSWorkspaceLaunchAndHide) - configuration:nil + configuration:[NSMutableDictionary dictionary] error:&err]) { NSLog(@"Unable to launch %@: %@", app_path, err); }