X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?p=xscreensaver;a=blobdiff_plain;f=OSX%2FXScreenSaverGLView.m;h=d61964f8d3b3fd0dad5fd909ecc3938c5b6be79b;hp=58144ee1c342b1645749d37571d6770c4cd46233;hb=4361b69d3178d7fc98d0388f9a223af6c2651aba;hpb=6f5482d73adb0165c0130bb47d852644ab0c4869 diff --git a/OSX/XScreenSaverGLView.m b/OSX/XScreenSaverGLView.m index 58144ee1..d61964f8 100644 --- a/OSX/XScreenSaverGLView.m +++ b/OSX/XScreenSaverGLView.m @@ -1,13 +1,13 @@ -/* xscreensaver, Copyright (c) 2006-2012 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 -* the above copyright notice appear in all copies and that both that -* copyright notice and this permission notice appear in supporting -* documentation. No representations are made about the suitability of this -* software for any purpose. It is provided "as is" without express or -* implied warranty. -*/ +/* 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 + * the above copyright notice appear in all copies and that both that + * copyright notice and this permission notice appear in supporting + * documentation. No representations are made about the suitability of this + * software for any purpose. It is provided "as is" without express or + * implied warranty. + */ /* This is a subclass of Apple's ScreenSaverView that knows how to run xscreensaver programs without X11 via the dark magic of the "jwxyz" @@ -17,11 +17,14 @@ #import "XScreenSaverGLView.h" #import "XScreenSaverConfigSheet.h" +#import "jwxyz-cocoa.h" +#import "jwxyzI.h" #import "screenhackI.h" #import "xlockmoreI.h" #ifdef USE_IPHONE # include "jwzgles.h" +# import #else # import #endif @@ -37,159 +40,97 @@ extern void check_gl_error (const char *type); @implementation XScreenSaverGLView -- (void)stopAnimation -{ - [super stopAnimation]; - - // Without this, the GL frame stays on screen when switching tabs - // in System Preferences. - // -# ifndef USE_IPHONE - [NSOpenGLContext clearCurrentContext]; -# endif // !USE_IPHONE - clear_gl_error(); // This hack is defunct, don't let this linger. +#ifdef USE_IPHONE +/* With GL programs, drawing at full resolution isn't a problem. + */ +- (CGFloat) hackedContentScaleFactor +{ + return [self contentScaleFactor]; } - -// #### maybe this could/should just be on 'lockFocus' instead? -- (void) prepareContext +- (BOOL)ignoreRotation { - if (ogl_ctx) { -#ifdef USE_IPHONE - [EAGLContext setCurrentContext:ogl_ctx]; -#else // !USE_IPHONE - [ogl_ctx makeCurrentContext]; -// check_gl_error ("makeCurrentContext"); - [ogl_ctx update]; -#endif // !USE_IPHONE - } + return FALSE; // Allow xwindow and the glViewport to change shape } - -- (void) resizeContext +- (BOOL) suppressRotationAnimation { -# ifndef USE_IPHONE - if (ogl_ctx) - [ogl_ctx setView:self]; -# endif // !USE_IPHONE + return _suppressRotationAnimation; // per-hack setting, default FALSE } - -- (NSOpenGLContext *) oglContext +- (BOOL) rotateTouches { - return ogl_ctx; + return TRUE; // We need the XY axes swapped in our events } -- (void) setOglContext: (NSOpenGLContext *) ctx +- (void) swapBuffers { - ogl_ctx = ctx; - -# ifdef USE_IPHONE - [EAGLContext setCurrentContext: ogl_ctx]; - - double s = self.contentScaleFactor; - int w = s * [self frame].size.width; - int h = s * [self frame].size.height; - - if (gl_framebuffer) glDeleteFramebuffersOES (1, &gl_framebuffer); - if (gl_renderbuffer) glDeleteRenderbuffersOES (1, &gl_renderbuffer); - if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer); - - glGenFramebuffersOES (1, &gl_framebuffer); - glBindFramebufferOES (GL_FRAMEBUFFER_OES, gl_framebuffer); - - glGenRenderbuffersOES (1, &gl_renderbuffer); +# ifdef JWXYZ_GL + GLint gl_renderbuffer = xwindow->gl_renderbuffer; +# endif // JWXYZ_GL glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer); + [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; +} +#endif // USE_IPHONE -// redundant? -// glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_RGBA8_OES, w, h); - [ogl_ctx renderbufferStorage:GL_RENDERBUFFER_OES - fromDrawable:(CAEAGLLayer*)self.layer]; - - glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, - GL_RENDERBUFFER_OES, gl_renderbuffer); - glGenRenderbuffersOES (1, &gl_depthbuffer); - // renderbufferStorage: must be called before this. - glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer); - glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, - w, h); - glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, - GL_RENDERBUFFER_OES, gl_depthbuffer); +- (void) animateOneFrame +{ +# if defined USE_IPHONE && defined JWXYZ_QUARTZ + UIGraphicsPushContext (backbuffer); +# endif - 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; - } + [self render_x11]; - check_gl_error ("setOglContext"); +# if defined USE_IPHONE && defined JWXYZ_QUARTZ + UIGraphicsPopContext(); +# endif +} -# endif // USE_IPHONE - [self resizeContext]; +/* GL screenhacks don't display a backbuffer, so this is a stub. */ +- (void) enableBackbuffer:(CGSize)new_backbuffer_size +{ } -#ifdef USE_IPHONE -+ (Class) layerClass + +/* GL screenhacks set their own viewport and matrices. */ +- (void) setViewport { - return [CAEAGLLayer class]; } -/* On MacOS: drawRect does nothing, and animateOneFrame renders. - On iOS GL: drawRect does nothing, and animateOneFrame renders. - On iOS X11: drawRect renders, and animateOneFrame marks the view dirty. +#ifdef USE_IPHONE + +/* Keep the GL scene oriented into a portrait-mode View, regardless of + what the physical device orientation is. */ -- (void)drawRect:(NSRect)rect +- (void) reshape_x11 { -} + [super reshape_x11]; + glMatrixMode(GL_PROJECTION); + glRotatef (-current_device_rotation(), 0, 0, 1); + glMatrixMode(GL_MODELVIEW); +} -- (void) animateOneFrame +- (void) render_x11 { - UIGraphicsPushContext (backbuffer); - [self render_x11]; - UIGraphicsPopContext(); + BOOL was_initted_p = initted_p; + [super render_x11]; + + if (! was_initted_p && xdpy) + _suppressRotationAnimation = + get_boolean_resource (xdpy, + "suppressRotationAnimation", + "SuppressRotationAnimation"); } +#endif // USE_IPHONE + + /* The backbuffer isn't actually used for GL programs, but it needs to be there for X11 calls to not error out. However, nothing done with @@ -197,34 +138,156 @@ extern void check_gl_error (const char *type); and discarded. That's ok, though, because mostly it's just calls to XClearWindow and housekeeping stuff like that. So we make a tiny one. */ -- (void) createBackbuffer +- (void) createBackbuffer:(CGSize)new_size { - // Don't resize the X11 window to match rotation. - // Rotation and scaling are handled in GL. - // - NSRect f = [self frame]; - double s = self.contentScaleFactor; - backbuffer_size.width = (int) (s * f.size.width); - backbuffer_size.height = (int) (s * f.size.height); +#ifdef JWXYZ_QUARTZ + NSAssert (! backbuffer_texture, + @"backbuffer_texture shouldn't be used for GL hacks"); if (! backbuffer) { CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); int w = 8; int h = 8; - backbuffer = CGBitmapContextCreate (NULL, w, h, + backbuffer = CGBitmapContextCreate (NULL, w, h, // yup, only 8px x 8px. 8, w*4, cs, - kCGImageAlphaPremultipliedLast); + (kCGBitmapByteOrder32Little | + kCGImageAlphaNoneSkipLast)); CGColorSpaceRelease (cs); } +#endif // JWXYZ_QUARTZ } -- (void) swapBuffers +/* Another stub for GL screenhacks. */ +- (void) drawBackbuffer +{ +} + + +/* Likewise. GL screenhacks control display with glXSwapBuffers(). */ +- (void) flushBackbuffer { - glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_renderbuffer); - [ogl_ctx presentRenderbuffer:GL_RENDERBUFFER_OES]; } -# endif // USE_IPHONE + + +#ifndef USE_IPHONE + +- (NSOpenGLPixelFormat *) getGLPixelFormat +{ + NSOpenGLPixelFormatAttribute attrs[40]; + int i = 0; + attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; + attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; + attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 24; + + if ([prefsReader getBooleanResource:"doubleBuffer"]) + attrs[i++] = NSOpenGLPFADoubleBuffer; + + Bool ms_p = [prefsReader getBooleanResource:"multiSample"]; + + /* Sometimes, turning on multisampling kills performance. At one point, + I thought the answer was, "only run multisampling on one screen, and + leave it turned off on other screens". That's what this code does, + but it turns out, that solution is insufficient. I can't really tell + what causes poor performance with multisampling, but it's not + predictable. Without changing the code, some times a given saver will + perform fine with multisampling on, and other times it will perform + very badly. Without multisampling, they always perform fine. + */ + // if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0]) + // ms_p = 0; + + if (ms_p) { + attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; + attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6; + // Don't really understand what this means: + // attrs[i++] = NSOpenGLPFANoRecovery; + } + + attrs[i++] = NSOpenGLPFAWindow; +# ifdef JWXYZ_GL + attrs[i++] = NSOpenGLPFAPixelBuffer; +# endif + + attrs[i] = 0; + + NSOpenGLPixelFormat *result = [[NSOpenGLPixelFormat alloc] + initWithAttributes:attrs]; + + if (ms_p && !result) { // Retry without multisampling. + i -= 2; + attrs[i] = 0; + result = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; + } + + return [result autorelease]; +} + +#else // !USE_IPHONE + +- (NSDictionary *)getGLProperties +{ + Bool dbuf_p = [prefsReader getBooleanResource:"doubleBuffer"]; + + /* There seems to be no way to actually turn off double-buffering in + EAGLContext (e.g., no way to draw to the front buffer directly) + but if we turn on "retained backing" for non-buffering apps like + "pipes", at least the back buffer isn't auto-cleared on them. + */ + + return [NSDictionary dictionaryWithObjectsAndKeys: + kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, + [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking, + nil]; +} + +- (void)addExtraRenderbuffers:(CGSize)size +{ + int w = size.width; + int h = size.height; + + if (gl_depthbuffer) glDeleteRenderbuffersOES (1, &gl_depthbuffer); + + glGenRenderbuffersOES (1, &gl_depthbuffer); + // [EAGLContext renderbufferStorage:fromDrawable:] must be called before this. + glBindRenderbufferOES (GL_RENDERBUFFER_OES, gl_depthbuffer); + glRenderbufferStorageOES (GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, + w, h); + glFramebufferRenderbufferOES (GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, + GL_RENDERBUFFER_OES, gl_depthbuffer); +} + +- (NSString *)getCAGravity +{ + return kCAGravityCenter; +} + +- (void) startAnimation +{ + [super startAnimation]; + if (ogl_ctx) /* Almost always true. */ + _glesState = jwzgles_make_state (); +} + +- (void) stopAnimation +{ + [super stopAnimation]; +#ifdef USE_IPHONE + if (_glesState) { + [EAGLContext setCurrentContext:ogl_ctx]; + jwzgles_make_current (_glesState); + jwzgles_free_state (); + } +#endif +} + +- (void) prepareContext +{ + [super prepareContext]; + jwzgles_make_current (_glesState); +} + +#endif // !USE_IPHONE - (void)dealloc { @@ -266,131 +329,20 @@ init_GL (ModeInfo *mi) XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (win); NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]], @"wrong view class: %@", view); - NSOpenGLContext *ctx = [view oglContext]; - -# ifndef USE_IPHONE - - if (!ctx) { - - NSOpenGLPixelFormatAttribute attrs[40]; - int i = 0; - attrs[i++] = NSOpenGLPFAColorSize; attrs[i++] = 24; - attrs[i++] = NSOpenGLPFAAlphaSize; attrs[i++] = 8; - attrs[i++] = NSOpenGLPFADepthSize; attrs[i++] = 16; - - if (get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer")) - attrs[i++] = NSOpenGLPFADoubleBuffer; - - Bool ms_p = get_boolean_resource (mi->dpy, "multiSample", "MultiSample"); - - /* Sometimes, turning on multisampling kills performance. At one point, - I thought the answer was, "only run multisampling on one screen, and - leave it turned off on other screens". That's what this code does, - but it turns out, that solution is insufficient. I can't really tell - what causes poor performance with multisampling, but it's not - predictable. Without changing the code, some times a given saver will - perform fine with multisampling on, and other times it will perform - very badly. Without multisampling, they always perform fine. - */ -// if (ms_p && [[view window] screen] != [[NSScreen screens] objectAtIndex:0]) -// ms_p = 0; - - if (ms_p) { - attrs[i++] = NSOpenGLPFASampleBuffers; attrs[i++] = 1; - attrs[i++] = NSOpenGLPFASamples; attrs[i++] = 6; - // Don't really understand what this means: - // attrs[i++] = NSOpenGLPFANoRecovery; - } - - attrs[i] = 0; - - NSOpenGLPixelFormat *pixfmt = - [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - - if (ms_p && !pixfmt) { // Retry without multisampling. - i -= 2; - attrs[i] = 0; - pixfmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attrs]; - } - NSAssert (pixfmt, @"unable to create NSOpenGLPixelFormat"); - - ctx = [[NSOpenGLContext alloc] - initWithFormat:pixfmt - shareContext:nil]; -// [pixfmt release]; // #### ??? - } + // OpenGL initialization is in [XScreenSaverView startAnimation]. - // Sync refreshes to the vertical blanking interval - GLint r = 1; - [ctx setValues:&r forParameter:NSOpenGLCPSwapInterval]; -// check_gl_error ("NSOpenGLCPSwapInterval"); // SEGV sometimes. Too early? - - // #### "Build and Analyze" says that ctx leaks, because it doesn't - // seem to realize that makeCurrentContext retains it (right?) - // Not sure what to do to make this warning go away. - - [ctx makeCurrentContext]; - check_gl_error ("makeCurrentContext"); - - [view setOglContext:ctx]; + // I don't know why this is necessary, but it beats randomly having some + // textures be upside down. + // + glMatrixMode(GL_TEXTURE); + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); - // 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); - } - } - - check_gl_error ("init_GL"); - -# else // USE_IPHONE - - EAGLContext *ogl_ctx = ctx; - if (!ogl_ctx) { - - Bool dbuf_p = - get_boolean_resource (mi->dpy, "doubleBuffer", "DoubleBuffer"); - - /* There seems to be no way to actually turn off double-buffering in - EAGLContext (e.g., no way to draw to the front buffer directly) - but if we turn on "retained backing" for non-buffering apps like - "pipes", at least the back buffer isn't auto-cleared on them. - */ - CAEAGLLayer *eagl_layer = (CAEAGLLayer *) view.layer; - eagl_layer.opaque = TRUE; - eagl_layer.drawableProperties = - [NSDictionary dictionaryWithObjectsAndKeys: - kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, - [NSNumber numberWithBool:!dbuf_p], kEAGLDrawablePropertyRetainedBacking, - nil]; - - // Without this, the GL frame buffer is half the screen resolution! - eagl_layer.contentsScale = [UIScreen mainScreen].scale; - - ogl_ctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1]; - } - - if (!ogl_ctx) - return 0; - [view setOglContext:ogl_ctx]; - - check_gl_error ("OES_init"); - -# endif // USE_IPHONE // Caller expects a pointer to an opaque struct... which it dereferences. // Don't ask me, it's historical... @@ -404,6 +356,11 @@ init_GL (ModeInfo *mi) void glXSwapBuffers (Display *dpy, Window window) { + // This all is very much like what's in -[XScreenSaverView flushBackbuffer]. +#ifdef JWXYZ_GL + jwxyz_bind_drawable (window, window); +#endif // JWXYZ_GL + XScreenSaverGLView *view = (XScreenSaverGLView *) jwxyz_window_view (window); NSAssert1 ([view isKindOfClass:[XScreenSaverGLView class]], @"wrong view class: %@", view); @@ -432,6 +389,12 @@ clear_gl_error (void) } +#if defined GL_INVALID_FRAMEBUFFER_OPERATION_OES && \ + !defined GL_INVALID_FRAMEBUFFER_OPERATION +# define GL_INVALID_FRAMEBUFFER_OPERATION GL_INVALID_FRAMEBUFFER_OPERATION_OES +#endif + + /* report a GL error. */ void check_gl_error (const char *type) @@ -447,6 +410,11 @@ check_gl_error (const char *type) case GL_STACK_OVERFLOW: e = "stack overflow"; break; case GL_STACK_UNDERFLOW: e = "stack underflow"; break; case GL_OUT_OF_MEMORY: e = "out of memory"; break; +#ifdef GL_INVALID_FRAMEBUFFER_OPERATION + case GL_INVALID_FRAMEBUFFER_OPERATION: + e = "invalid framebuffer operation"; + break; +#endif #ifdef GL_TABLE_TOO_LARGE_EXT case GL_TABLE_TOO_LARGE_EXT: e = "table too large"; break; #endif